* take notifs off of message queue

* fix order of notif handling

* table

* reintroducing message queue

* setting up user

* added labeler classes

* tidy + hook up labeler to config / service start

* hooking up p-queue to labeler

* rip out message queue

* drop mq tables

* rm stream consumers

* keyword labeler

* tidy + some tests

* work labels into views

* update snaps

* labeled images in dev-env

* snaps

* labeler tests

* more labels

* Update packages/pds/src/api/app/bsky/notification/listNotifications.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* Update packages/dev-env/src/mock/index.ts

* Update packages/dev-env/src/mock/index.ts

* Update packages/pds/src/labeler/util.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* Update packages/pds/tests/labeler/hive.test.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* pr feedback + migration change

* fix lower case keywords issue

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
This commit is contained in:
Daniel Holmgren 2023-04-12 14:34:18 -05:00 committed by GitHub
parent d8b975d8dd
commit 8b1da9fed8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2235 additions and 194 deletions

@ -5,40 +5,36 @@
"label": {
"type": "object",
"description": "Metadata tag on an atproto resource (eg, repo or record)",
"key": "tid",
"record": {
"type": "object",
"required": ["src", "uri", "val", "cts"],
"properties": {
"src": {
"type": "string",
"format": "did",
"description": "DID of the actor who created this label"
},
"uri": {
"type": "string",
"format": "uri",
"description": "AT URI of the record, repository (account), or other resource which this label applies to"
},
"cid": {
"type": "string",
"format": "cid",
"description": "optionally, CID specifying the specific version of 'uri' resource this label applies to"
},
"val": {
"type": "string",
"maxLength": 128,
"description": "the short string name of the value or type of this label"
},
"neg": {
"type": "boolean",
"description": "if true, this is a negation label, overwriting a previous label"
},
"cts": {
"type": "string",
"format": "datetime",
"description": "timestamp when this label was created"
}
"required": ["src", "uri", "val", "cts"],
"properties": {
"src": {
"type": "string",
"format": "did",
"description": "DID of the actor who created this label"
},
"uri": {
"type": "string",
"format": "uri",
"description": "AT URI of the record, repository (account), or other resource which this label applies to"
},
"cid": {
"type": "string",
"format": "cid",
"description": "optionally, CID specifying the specific version of 'uri' resource this label applies to"
},
"val": {
"type": "string",
"maxLength": 128,
"description": "the short string name of the value or type of this label"
},
"neg": {
"type": "boolean",
"description": "if true, this is a negation label, overwriting a previous label"
},
"cts": {
"type": "string",
"format": "datetime",
"description": "timestamp when this label was created"
}
}
}

@ -1113,44 +1113,40 @@ export const schemaDict = {
label: {
type: 'object',
description: 'Metadata tag on an atproto resource (eg, repo or record)',
key: 'tid',
record: {
type: 'object',
required: ['src', 'uri', 'val', 'cts'],
properties: {
src: {
type: 'string',
format: 'did',
description: 'DID of the actor who created this label',
},
uri: {
type: 'string',
format: 'uri',
description:
'AT URI of the record, repository (account), or other resource which this label applies to',
},
cid: {
type: 'string',
format: 'cid',
description:
"optionally, CID specifying the specific version of 'uri' resource this label applies to",
},
val: {
type: 'string',
maxLength: 128,
description:
'the short string name of the value or type of this label',
},
neg: {
type: 'boolean',
description:
'if true, this is a negation label, overwriting a previous label',
},
cts: {
type: 'string',
format: 'datetime',
description: 'timestamp when this label was created',
},
required: ['src', 'uri', 'val', 'cts'],
properties: {
src: {
type: 'string',
format: 'did',
description: 'DID of the actor who created this label',
},
uri: {
type: 'string',
format: 'uri',
description:
'AT URI of the record, repository (account), or other resource which this label applies to',
},
cid: {
type: 'string',
format: 'cid',
description:
"optionally, CID specifying the specific version of 'uri' resource this label applies to",
},
val: {
type: 'string',
maxLength: 128,
description:
'the short string name of the value or type of this label',
},
neg: {
type: 'boolean',
description:
'if true, this is a negation label, overwriting a previous label',
},
cts: {
type: 'string',
format: 'datetime',
description: 'timestamp when this label was created',
},
},
},

@ -7,7 +7,21 @@ import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
/** Metadata tag on an atproto resource (eg, repo or record) */
export interface Label {}
export interface Label {
/** DID of the actor who created this label */
src: string
/** AT URI of the record, repository (account), or other resource which this label applies to */
uri: string
/** optionally, CID specifying the specific version of 'uri' resource this label applies to */
cid?: string
/** the short string name of the value or type of this label */
val: string
/** if true, this is a negation label, overwriting a previous label */
neg?: boolean
/** timestamp when this label was created */
cts: string
[k: string]: unknown
}
export function isLabel(v: unknown): v is Label {
return (

@ -30,6 +30,14 @@ export const streamSize = async (stream: Readable): Promise<number> => {
return size
}
export const streamToBytes = async (stream: Readable): Promise<Uint8Array> => {
const bufs: Buffer[] = []
for await (const bytes of stream) {
bufs.push(bytes)
}
return new Uint8Array(Buffer.concat(bufs))
}
export const bytesToStream = (bytes: Uint8Array): Readable => {
const stream = new Readable()
stream.push(bytes)

@ -5,6 +5,7 @@ import PDSServer, {
Database as PDSDatabase,
MemoryBlobStore,
ServerConfig as PDSServerConfig,
AppContext as PDSContext,
} from '@atproto/pds'
import * as plc from '@did-plc/lib'
import { PlcServer, Database as PlcDatabase } from '@did-plc/server'
@ -45,6 +46,12 @@ export class DevEnvServer {
return `http://localhost:${this.port}`
}
get ctx(): PDSContext | undefined {
if (this.inst instanceof PDSServer) {
return this.inst.ctx
}
}
async start() {
if (this.inst) {
throw new Error('Already started')
@ -107,6 +114,8 @@ export class DevEnvServer {
'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8',
privacyPolicyUrl: 'https://example.com/privacy',
termsOfServiceUrl: 'https://example.com/tos',
labelerDid: 'did:example:labeler',
labelerKeywords: {},
maxSubscriptionBuffer: 200,
repoBackfillLimitMs: HOUR,
appViewRepoProvider: process.env.APP_VIEW_REPO_PROVIDER,

@ -8,6 +8,7 @@ import { DevEnv } from '../index'
import { ServerType } from '../types'
import { genServerCfg } from '../util'
import { postTexts, replyTexts } from './data'
import labeledImgB64 from './labeled-img-b64'
// NOTE
// deterministic date generator
@ -164,6 +165,61 @@ export async function generateMockSetup(env: DevEnv) {
}
}
// make some naughty posts & label them
const file = Buffer.from(labeledImgB64, 'base64')
const uploadedImg = await bob.agent.api.com.atproto.repo.uploadBlob(file, {
encoding: 'image/png',
})
const labeledPost = await bob.agent.api.app.bsky.feed.post.create(
{ repo: bob.did },
{
text: 'naughty post',
embed: {
$type: 'app.bsky.embed.images',
images: [
{
image: uploadedImg.data.blob,
alt: 'naughty naughty',
},
],
},
createdAt: date.next().value,
},
)
const filteredPost = await bob.agent.api.app.bsky.feed.post.create(
{ repo: bob.did },
{
text: 'reallly bad post should be deleted',
createdAt: date.next().value,
},
)
const ctx = env.listOfType(ServerType.PersonalDataServer)[0].ctx
if (ctx) {
await ctx.db.db
.insertInto('label')
.values([
{
sourceDid: ctx.cfg.labelerDid,
subjectUri: labeledPost.uri,
subjectCid: labeledPost.cid,
value: 'nudity',
negated: 0,
createdAt: new Date().toISOString(),
},
{
sourceDid: ctx.cfg.labelerDid,
subjectUri: filteredPost.uri,
subjectCid: filteredPost.cid,
value: 'dmca-violation',
negated: 0,
createdAt: new Date().toISOString(),
},
])
.execute()
}
// a set of replies
for (let i = 0; i < 100; i++) {
const targetUri = picka(posts).uri

File diff suppressed because one or more lines are too long

@ -43,6 +43,7 @@
"express": "^4.17.2",
"express-async-errors": "^3.1.1",
"file-type": "^16.5.4",
"form-data": "^4.0.0",
"handlebars": "^4.7.7",
"http-errors": "^2.0.0",
"http-terminator": "^3.2.0",

@ -75,13 +75,18 @@ export default function (server: Server, ctx: AppContext) {
// @NOTE calling into app-view, will eventually be replaced
const actorService = ctx.services.appView.actor(ctx.db)
const authors = await actorService.views.profile(
notifs.map((notif) => ({
did: notif.authorDid,
handle: notif.authorHandle,
})),
requester,
)
const labelService = ctx.services.appView.label(ctx.db)
const recordUris = notifs.map((notif) => notif.uri)
const [authors, labels] = await Promise.all([
actorService.views.profile(
notifs.map((notif) => ({
did: notif.authorDid,
handle: notif.authorHandle,
})),
requester,
),
labelService.getLabels(recordUris),
])
const notifications = notifs.map((notif, i) => ({
uri: notif.uri,
@ -92,6 +97,7 @@ export default function (server: Server, ctx: AppContext) {
record: common.cborBytesToRecord(notif.recordBytes),
isRead: notif.indexedAt <= userState.lastSeenNotifs,
indexedAt: notif.indexedAt,
labels: labels[notif.uri] ?? [],
}))
return {

@ -14,6 +14,7 @@ export default function (server: Server, ctx: AppContext) {
const { ref } = db.dynamic
const feedService = ctx.services.appView.feed(ctx.db)
const labelService = ctx.services.appView.label(ctx.db)
const userLookupCol = actor.startsWith('did:')
? 'did_handle.did'
@ -50,7 +51,12 @@ export default function (server: Server, ctx: AppContext) {
})
const feedItems: FeedRow[] = await feedItemsQb.execute()
const feed = await composeFeed(feedService, feedItems, requester)
const feed = await composeFeed(
feedService,
labelService,
feedItems,
requester,
)
return {
encoding: 'application/json',

@ -8,6 +8,7 @@ import {
FeedService,
PostInfoMap,
} from '../../../../services/feed'
import { Labels } from '../../../../services/label'
export type PostThread = {
post: FeedRow
@ -23,16 +24,18 @@ export default function (server: Server, ctx: AppContext) {
const requester = auth.credentials.did
const feedService = ctx.services.appView.feed(ctx.db)
const labelService = ctx.services.appView.label(ctx.db)
const threadData = await getThreadData(feedService, uri, depth)
if (!threadData) {
throw new InvalidRequestError(`Post not found: ${uri}`, 'NotFound')
}
const relevant = getRelevantIds(threadData)
const [actors, posts, embeds] = await Promise.all([
const [actors, posts, embeds, labels] = await Promise.all([
feedService.getActorViews(Array.from(relevant.dids), requester),
feedService.getPostViews(Array.from(relevant.uris), requester),
feedService.embedsForPosts(Array.from(relevant.uris), requester),
labelService.getLabels(Array.from(relevant.uris)),
])
const thread = composeThread(
@ -41,6 +44,7 @@ export default function (server: Server, ctx: AppContext) {
posts,
actors,
embeds,
labels,
)
return {
encoding: 'application/json',
@ -56,12 +60,14 @@ const composeThread = (
posts: PostInfoMap,
actors: ActorViewMap,
embeds: FeedEmbeds,
labels: Labels,
) => {
const post = feedService.formatPostView(
threadData.post.postUri,
actors,
posts,
embeds,
labels,
)
let parent
@ -79,6 +85,7 @@ const composeThread = (
posts,
actors,
embeds,
labels,
)
}
}
@ -86,7 +93,7 @@ const composeThread = (
let replies
if (threadData.replies) {
replies = threadData.replies.map((reply) =>
composeThread(reply, feedService, posts, actors, embeds),
composeThread(reply, feedService, posts, actors, embeds, labels),
)
}

@ -21,6 +21,7 @@ export default function (server: Server, ctx: AppContext) {
}
const feedService = ctx.services.appView.feed(ctx.db)
const labelService = ctx.services.appView.label(ctx.db)
const followingIdsSubquery = db
.selectFrom('follow')
@ -58,7 +59,12 @@ export default function (server: Server, ctx: AppContext) {
keyset,
})
const feedItems: FeedRow[] = await feedItemsQb.execute()
const feed = await composeFeed(feedService, feedItems, requester)
const feed = await composeFeed(
feedService,
labelService,
feedItems,
requester,
)
return {
encoding: 'application/json',

@ -17,6 +17,7 @@ export default function (server: Server, ctx: AppContext) {
const { ref } = db.dynamic
const feedService = ctx.services.appView.feed(ctx.db)
const labelService = ctx.services.appView.label(ctx.db)
const postsQb = feedService
.selectPostQb()
@ -55,6 +56,7 @@ export default function (server: Server, ctx: AppContext) {
const feedItems: FeedRow[] = await feedQb.execute()
const feed: FeedViewPost[] = await composeFeed(
feedService,
labelService,
feedItems,
requester,
)

@ -2,11 +2,13 @@ import { AtUri } from '@atproto/uri'
import { TimeCidKeyset } from '../../../../../db/pagination'
import { FeedViewPost } from '../../../../../lexicon/types/app/bsky/feed/defs'
import { FeedRow, FeedService } from '../../../../services/feed'
import { LabelService } from '../../../../services/label'
// Present post and repost results into FeedViewPosts
// Including links to embedded media
export const composeFeed = async (
feedService: FeedService,
labelService: LabelService,
rows: FeedRow[],
requester: string,
): Promise<FeedViewPost[]> => {
@ -25,15 +27,22 @@ export const composeFeed = async (
actorDids.add(new AtUri(row.replyRoot).host)
}
}
const [actors, posts, embeds] = await Promise.all([
const [actors, posts, embeds, labels] = await Promise.all([
feedService.getActorViews(Array.from(actorDids), requester),
feedService.getPostViews(Array.from(postUris), requester),
feedService.embedsForPosts(Array.from(postUris), requester),
labelService.getLabels(Array.from(postUris)),
])
const feed: FeedViewPost[] = []
for (const row of rows) {
const post = feedService.formatPostView(row.postUri, actors, posts, embeds)
const post = feedService.formatPostView(
row.postUri,
actors,
posts,
embeds,
labels,
)
const originator = actors[row.originatorDid]
if (post && originator) {
let reasonType: string | undefined
@ -41,10 +50,22 @@ export const composeFeed = async (
reasonType = 'app.bsky.feed.defs#reasonRepost'
}
const replyParent = row.replyParent
? feedService.formatPostView(row.replyParent, actors, posts, embeds)
? feedService.formatPostView(
row.replyParent,
actors,
posts,
embeds,
labels,
)
: undefined
const replyRoot = row.replyRoot
? feedService.formatPostView(row.replyRoot, actors, posts, embeds)
? feedService.formatPostView(
row.replyRoot,
actors,
posts,
embeds,
labels,
)
: undefined
feed.push({

@ -8,10 +8,15 @@ import { DidHandle } from '../../../db/tables/did-handle'
import { countAll } from '../../../db/util'
import Database from '../../../db'
import { ImageUriBuilder } from '../../../image/uri'
import { LabelService } from '../label'
export class ActorViews {
constructor(private db: Database, private imgUriBuilder: ImageUriBuilder) {}
services = {
label: LabelService.creator(),
}
profileDetailed(
result: ActorResult,
viewer: string,
@ -29,13 +34,11 @@ export class ActorViews {
const { ref } = this.db.db.dynamic
const profileInfos = await this.db.db
const dids = results.map((r) => r.did)
const profileInfosQb = this.db.db
.selectFrom('did_handle')
.where(
'did_handle.did',
'in',
results.map((r) => r.did),
)
.where('did_handle.did', 'in', dids)
.leftJoin('profile', 'profile.creator', 'did_handle.did')
.select([
'did_handle.did as did',
@ -81,6 +84,11 @@ export class ActorViews {
])
.execute()
const [profileInfos, labels] = await Promise.all([
profileInfosQb,
this.services.label(this.db).getLabelsForProfiles(dids),
])
const profileInfoByDid = profileInfos.reduce((acc, info) => {
return Object.assign(acc, { [info.did]: info })
}, {} as Record<string, ArrayEl<typeof profileInfos>>)
@ -109,6 +117,7 @@ export class ActorViews {
following: profileInfo?.requesterFollowing || undefined,
followedBy: profileInfo?.requesterFollowedBy || undefined,
},
labels: labels[result.did] ?? [],
}
})
@ -125,14 +134,11 @@ export class ActorViews {
if (results.length === 0) return []
const { ref } = this.db.db.dynamic
const dids = results.map((r) => r.did)
const profileInfos = await this.db.db
const profileInfosQb = this.db.db
.selectFrom('did_handle')
.where(
'did_handle.did',
'in',
results.map((r) => r.did),
)
.where('did_handle.did', 'in', dids)
.leftJoin('profile', 'profile.creator', 'did_handle.did')
.select([
'did_handle.did as did',
@ -162,6 +168,11 @@ export class ActorViews {
])
.execute()
const [profileInfos, labels] = await Promise.all([
profileInfosQb,
this.services.label(this.db).getLabelsForProfiles(dids),
])
const profileInfoByDid = profileInfos.reduce((acc, info) => {
return Object.assign(acc, { [info.did]: info })
}, {} as Record<string, ArrayEl<typeof profileInfos>>)
@ -183,6 +194,7 @@ export class ActorViews {
following: profileInfo?.requesterFollowing || undefined,
followedBy: profileInfo?.requesterFollowedBy || undefined,
},
labels: labels[result.did] ?? [],
}
})
@ -209,6 +221,7 @@ export class ActorViews {
displayName: truncateUtf8(view.displayName, 64) || undefined,
avatar: view.avatar,
viewer: view.viewer,
labels: view.labels,
}))
return Array.isArray(result) ? views : views[0]

@ -8,12 +8,17 @@ import { isView as isViewExternal } from '../../../lexicon/types/app/bsky/embed/
import { View as ViewRecord } from '../../../lexicon/types/app/bsky/embed/record'
import { PostView } from '../../../lexicon/types/app/bsky/feed/defs'
import { ActorViewMap, FeedEmbeds, PostInfoMap, FeedItemType } from './types'
import { Labels, LabelService } from '../label'
export * from './types'
export class FeedService {
constructor(public db: Database, public imgUriBuilder: ImageUriBuilder) {}
services = {
label: LabelService.creator(),
}
static creator(imgUriBuilder: ImageUriBuilder) {
return (db: Database) => new FeedService(db, imgUriBuilder)
}
@ -222,21 +227,17 @@ export class FeedService {
extPromise,
recordPromise,
])
const [postViews, actorViews, deepEmbedViews] = await Promise.all([
this.getPostViews(
records.map((p) => p.uri),
requester,
),
this.getActorViews(
records.map((p) => p.did),
requester,
),
this.embedsForPosts(
records.map((p) => p.uri),
requester,
_depth + 1,
),
])
const nestedUris = records.map((p) => p.uri)
const [postViews, actorViews, deepEmbedViews, labelViews] =
await Promise.all([
this.getPostViews(nestedUris, requester),
this.getActorViews(
records.map((p) => p.did),
requester,
),
this.embedsForPosts(nestedUris, requester, _depth + 1),
this.services.label(this.db).getLabels(nestedUris),
])
let embeds = images.reduce((acc, cur) => {
const embed = (acc[cur.postUri] ??= {
$type: 'app.bsky.embed.images#view',
@ -281,6 +282,7 @@ export class FeedService {
actorViews,
postViews,
deepEmbedViews,
labelViews,
)
let deepEmbeds: ViewRecord['embeds'] | undefined
if (_depth < 1) {
@ -330,6 +332,7 @@ export class FeedService {
actors: ActorViewMap,
posts: PostInfoMap,
embeds: FeedEmbeds,
labels: Labels,
): PostView | undefined {
const post = posts[uri]
const author = actors[post?.creator]
@ -348,6 +351,7 @@ export class FeedService {
repost: post.requesterRepost ?? undefined,
like: post.requesterLike ?? undefined,
},
labels: labels[uri] ?? [],
}
}
}

@ -8,6 +8,7 @@ import * as Repost from './plugins/repost'
import * as Follow from './plugins/follow'
import * as Profile from './plugins/profile'
import { MessageQueue } from '../../../event-stream/types'
import { Labeler } from '../../../labeler'
export class IndexingService {
records: {
@ -18,7 +19,11 @@ export class IndexingService {
profile: Profile.PluginType
}
constructor(public db: Database, public messageDispatcher: MessageQueue) {
constructor(
public db: Database,
public labeler: Labeler,
public messageDispatcher: MessageQueue,
) {
this.records = {
post: Post.makePlugin(this.db.db),
like: Like.makePlugin(this.db.db),
@ -28,8 +33,8 @@ export class IndexingService {
}
}
static creator(messageDispatcher: MessageQueue) {
return (db: Database) => new IndexingService(db, messageDispatcher)
static creator(labeler: Labeler, messageDispatcher: MessageQueue) {
return (db: Database) => new IndexingService(db, labeler, messageDispatcher)
}
async indexRecord(
@ -46,6 +51,7 @@ export class IndexingService {
} else {
await indexer.updateRecord(uri, cid, obj, timestamp)
}
this.labeler.processRecord(uri, obj)
}
async deleteRecord(uri: AtUri, cascading = false) {

@ -144,7 +144,7 @@ export class RecordProcessor<T, S> {
.where('duplicateOf', '=', uri.toString())
.execute()
await this.handleNotifs({ deleted })
return []
return
} else {
const found = await this.db
.selectFrom('duplicate_record')

@ -0,0 +1,43 @@
import { AtUri } from '@atproto/uri'
import Database from '../../../db'
import { Label } from '../../../lexicon/types/com/atproto/label/defs'
export type Labels = Record<string, Label[]>
export class LabelService {
constructor(public db: Database) {}
static creator() {
return (db: Database) => new LabelService(db)
}
async getLabels(subjects: string[]): Promise<Labels> {
if (subjects.length < 1) return {}
const res = await this.db.db
.selectFrom('label')
.where('label.subjectUri', 'in', subjects)
.selectAll()
.execute()
return res.reduce((acc, cur) => {
acc[cur.subjectUri] ??= []
acc[cur.subjectUri].push({
src: cur.sourceDid,
uri: cur.subjectUri,
cid: cur.subjectCid,
val: cur.value,
neg: cur.negated === 1, // @TODO update in appview
cts: cur.createdAt,
})
return acc
}, {} as Labels)
}
async getLabelsForProfiles(dids: string[]): Promise<Labels> {
if (dids.length < 1) return {}
const profileUris = dids.map((did) =>
AtUri.make(did, 'app.bsky.feed.profile', 'self').toString(),
)
const subjects = [...dids, ...profileUris]
return this.getLabels(subjects)
}
}

@ -41,6 +41,10 @@ export interface ServerConfigValues {
emailSmtpUrl?: string
emailNoReplyAddress: string
hiveApiKey?: string
labelerDid: string
labelerKeywords: Record<string, string>
maxSubscriptionBuffer: number
repoBackfillLimitMs: number
@ -120,6 +124,10 @@ export class ServerConfig {
const emailNoReplyAddress =
process.env.EMAIL_NO_REPLY_ADDRESS || 'noreply@blueskyweb.xyz'
const hiveApiKey = process.env.HIVE_API_KEY || undefined
const labelerDid = process.env.LABELER_DID || 'did:example:labeler'
const labelerKeywords = {}
const dbPostgresUrl = process.env.DB_POSTGRES_URL
const dbPostgresSchema = process.env.DB_POSTGRES_SCHEMA
@ -165,6 +173,9 @@ export class ServerConfig {
appUrlPasswordReset,
emailSmtpUrl,
emailNoReplyAddress,
hiveApiKey,
labelerDid,
labelerKeywords,
maxSubscriptionBuffer,
repoBackfillLimitMs,
appViewRepoProvider,
@ -314,6 +325,18 @@ export class ServerConfig {
return this.cfg.emailNoReplyAddress
}
get hiveApiKey() {
return this.cfg.hiveApiKey
}
get labelerDid() {
return this.cfg.labelerDid
}
get labelerKeywords() {
return this.cfg.labelerKeywords
}
get maxSubscriptionBuffer() {
return this.cfg.maxSubscriptionBuffer
}

@ -10,6 +10,7 @@ import { ImageUriBuilder } from './image/uri'
import { Services } from './services'
import { MessageDispatcher } from './event-stream/message-queue'
import Sequencer from './sequencer'
import { Labeler } from './labeler'
export class AppContext {
constructor(
@ -25,6 +26,7 @@ export class AppContext {
services: Services
messageDispatcher: MessageDispatcher
sequencer: Sequencer
labeler: Labeler
},
) {}
@ -88,6 +90,10 @@ export class AppContext {
return this.opts.sequencer
}
get labeler(): Labeler {
return this.opts.labeler
}
get plcClient(): plc.Client {
return new plc.Client(this.cfg.didPlcUrl)
}

@ -16,6 +16,7 @@ import * as repoBlob from './tables/repo-blob'
import * as deleteAccountToken from './tables/delete-account-token'
import * as moderation from './tables/moderation'
import * as mute from './tables/mute'
import * as label from './tables/label'
import * as repoSeq from './tables/repo-seq'
import * as appMigration from './tables/app-migration'
import * as appView from '../app-view/db'
@ -41,6 +42,7 @@ export type DatabaseSchemaType = appView.DatabaseSchemaType &
deleteAccountToken.PartialDB &
moderation.PartialDB &
mute.PartialDB &
label.PartialDB &
repoSeq.PartialDB
export type DatabaseSchema = Kysely<DatabaseSchemaType>

@ -0,0 +1,29 @@
import { Kysely } from 'kysely'
export async function up(db: Kysely<unknown>): Promise<void> {
await db.schema
.createTable('label')
.addColumn('sourceDid', 'varchar', (col) => col.notNull())
.addColumn('subjectUri', 'varchar', (col) => col.notNull())
.addColumn('subjectCid', 'varchar', (col) => col.notNull())
.addColumn('value', 'varchar', (col) => col.notNull())
.addColumn('negated', 'int2', (col) => col.notNull()) // @TODO convert to boolean in appview
.addColumn('createdAt', 'varchar', (col) => col.notNull())
.addPrimaryKeyConstraint('label_pkey', [
'sourceDid',
'subjectUri',
'subjectCid',
'value',
])
.execute()
await db.schema
.createIndex('label_subject_uri_index')
.on('label')
.column('subjectUri')
.execute()
}
export async function down(db: Kysely<unknown>): Promise<void> {
await db.schema.dropTable('label').execute()
}

@ -37,3 +37,4 @@ export * as _20230328T214311004Z from './20230328T214311004Z-profile-display-nam
export * as _20230328T214311005Z from './20230328T214311005Z-rework-seq'
export * as _20230406T185855842Z from './20230406T185855842Z-feed-item-init'
export * as _20230411T175730759Z from './20230411T175730759Z-drop-message-queue'
export * as _20230411T180247652Z from './20230411T180247652Z-labels'

@ -0,0 +1,12 @@
export const tableName = 'label'
export interface Label {
sourceDid: string
subjectUri: string
subjectCid: string
value: string
negated: 0 | 1 // @TODO convert to boolean in app-view
createdAt: string
}
export type PartialDB = { [tableName]: Label }

@ -31,6 +31,7 @@ import {
ImageInvalidator,
ImageProcessingServerInvalidator,
} from './image/invalidator'
import { Labeler, HiveLabeler, KeywordLabeler } from './labeler'
export type { ServerConfigValues } from './config'
export { ServerConfig } from './config'
@ -113,12 +114,31 @@ export class PDS {
config.imgUriKey,
)
let labeler: Labeler
if (config.hiveApiKey) {
labeler = new HiveLabeler({
db,
blobstore,
labelerDid: config.labelerDid,
hiveApiKey: config.hiveApiKey,
keywords: config.labelerKeywords,
})
} else {
labeler = new KeywordLabeler({
db,
blobstore,
labelerDid: config.labelerDid,
keywords: config.labelerKeywords,
})
}
const services = createServices({
repoSigningKey,
messageDispatcher,
blobstore,
imgUriBuilder,
imgInvalidator,
labeler,
})
const ctx = new AppContext({
@ -130,6 +150,7 @@ export class PDS {
auth,
messageDispatcher,
sequencer,
labeler,
services,
mailer,
imgUriBuilder,
@ -174,6 +195,7 @@ export class PDS {
async destroy(): Promise<void> {
this.appView.destroy()
await this.ctx.labeler.destroy()
await this.terminator?.terminate()
await this.ctx.db.close()
}

@ -0,0 +1,82 @@
import stream from 'stream'
import PQueue from 'p-queue'
import Database from '../db'
import { BlobStore, cidForRecord } from '@atproto/repo'
import { dedupe, getFieldsFromRecord } from './util'
import { AtUri } from '@atproto/uri'
import { labelerLogger as log } from '../logger'
export abstract class Labeler {
public db: Database
public blobstore: BlobStore
public labelerDid: string
public processingQueue: PQueue | null // null during teardown
constructor(opts: {
db: Database
blobstore: BlobStore
labelerDid: string
}) {
this.db = opts.db
this.blobstore = opts.blobstore
this.labelerDid = opts.labelerDid
this.processingQueue = new PQueue()
}
processRecord(uri: AtUri, obj: unknown) {
this.processingQueue?.add(() =>
this.createAndStoreLabels(uri, obj).catch((err) => {
log.error(
{ err, uri: uri.toString(), record: obj },
'failed to label record',
)
}),
)
}
async createAndStoreLabels(uri: AtUri, obj: unknown): Promise<void> {
const labels = await this.labelRecord(obj)
if (labels.length < 1) return
const cid = await cidForRecord(obj)
const rows = labels.map((value) => ({
sourceDid: this.labelerDid,
subjectUri: uri.toString(),
subjectCid: cid.toString(),
value,
negated: 0 as const,
createdAt: new Date().toISOString(),
}))
await this.db.db
.insertInto('label')
.values(rows)
.onConflict((oc) => oc.doNothing())
.execute()
}
async labelRecord(obj: unknown): Promise<string[]> {
const { text, imgs } = getFieldsFromRecord(obj)
const txtLabels = await this.labelText(text.join(' '))
const imgLabels = await Promise.all(
imgs.map(async (cid) => {
const stream = await this.blobstore.getStream(cid)
return this.labelImg(stream)
}),
)
return dedupe([...txtLabels, ...imgLabels.flat()])
}
abstract labelText(text: string): Promise<string[]>
abstract labelImg(img: stream.Readable): Promise<string[]>
async processAll() {
await this.processingQueue?.onIdle()
}
async destroy() {
const pQueue = this.processingQueue
this.processingQueue = null
pQueue?.pause()
pQueue?.clear()
await pQueue?.onIdle()
}
}

@ -0,0 +1,118 @@
import stream from 'stream'
import axios from 'axios'
import FormData from 'form-data'
import { Labeler } from './base'
import Database from '../db'
import { BlobStore } from '@atproto/repo'
import { keywordLabeling } from './util'
const HIVE_ENDPOINT = 'https://api.thehive.ai/api/v2/task/sync'
export class HiveLabeler extends Labeler {
hiveApiKey: string
keywords: Record<string, string>
constructor(opts: {
db: Database
blobstore: BlobStore
labelerDid: string
hiveApiKey: string
keywords: Record<string, string>
}) {
const { db, blobstore, labelerDid, hiveApiKey, keywords } = opts
super({ db, blobstore, labelerDid })
this.hiveApiKey = hiveApiKey
this.keywords = keywords
}
async labelText(text: string): Promise<string[]> {
return keywordLabeling(this.keywords, text)
}
async labelImg(img: stream.Readable): Promise<string[]> {
return labelBlob(img, this.hiveApiKey)
}
}
export const labelBlob = async (
blob: stream.Readable,
hiveApiKey: string,
): Promise<string[]> => {
const classes = await makeHiveReq(blob, hiveApiKey)
return summarizeLabels(classes)
}
export const makeHiveReq = async (
blob: stream.Readable,
hiveApiKey: string,
): Promise<HiveRespClass[]> => {
const form = new FormData()
form.append('media', blob)
const res = await axios.post(HIVE_ENDPOINT, form, {
headers: {
'Content-Type': 'multipart/form-data',
authorization: `token ${hiveApiKey}`,
accept: 'application/json',
},
})
return respToClasses(res.data)
}
export const respToClasses = (res: HiveResp): HiveRespClass[] => {
const classes: HiveRespClass[] = []
for (const status of res.status) {
for (const out of status.response.output) {
for (const cls of out.classes) {
classes.push(cls)
}
}
}
return classes
}
// sexual: https://docs.thehive.ai/docs/sexual-content
// gore and violence: https://docs.thehive.ai/docs/class-descriptions-violence-gore
// iconography: https://docs.thehive.ai/docs/class-descriptions-hate-bullying
const labelForClass = {
yes_sexual_activity: 'porn',
animal_genitalia_and_human: 'porn', // for some reason not included in 'yes_sexual_activity'
yes_male_nudity: 'nude',
yes_female_nudity: 'nude',
general_suggestive: 'sexual',
very_bloody: 'gore',
human_corpse: 'corpse',
yes_self_harm: 'self-harm',
yes_nazi: 'icon-nazi',
yes_kkk: 'icon-kkk',
yes_confederate: 'icon-confederate',
}
export const summarizeLabels = (classes: HiveRespClass[]): string[] => {
const labels: string[] = []
for (const cls of classes) {
if (labelForClass[cls.class] && cls.score >= 0.9) {
labels.push(labelForClass[cls.class])
}
}
return labels
}
type HiveResp = {
status: HiveRespStatus[]
}
type HiveRespStatus = {
response: {
output: HiveRespOutput[]
}
}
type HiveRespOutput = {
time: number
classes: HiveRespClass[]
}
type HiveRespClass = {
class: string
score: number
}

@ -0,0 +1,3 @@
export * from './base'
export * from './hive'
export * from './keyword'

@ -0,0 +1,27 @@
import { BlobStore } from '@atproto/repo'
import Database from '../db'
import { Labeler } from './base'
import { keywordLabeling } from './util'
export class KeywordLabeler extends Labeler {
keywords: Record<string, string>
constructor(opts: {
db: Database
blobstore: BlobStore
labelerDid: string
keywords: Record<string, string>
}) {
const { db, blobstore, labelerDid, keywords } = opts
super({ db, blobstore, labelerDid })
this.keywords = keywords
}
async labelText(text: string): Promise<string[]> {
return keywordLabeling(this.keywords, text)
}
async labelImg(): Promise<string[]> {
return []
}
}

@ -0,0 +1,108 @@
import { CID } from 'multiformats/cid'
import * as lex from '../lexicon/lexicons'
import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post'
import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile'
import { isMain as isEmbedImage } from '../lexicon/types/app/bsky/embed/images'
import { isMain as isEmbedExternal } from '../lexicon/types/app/bsky/embed/external'
import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia'
type RecordFields = {
text: string[]
imgs: CID[]
}
export const getFieldsFromRecord = (record: unknown): RecordFields => {
if (isPost(record)) {
return getFieldsFromPost(record)
} else if (isProfile(record)) {
return getFieldsFromProfile(record)
} else {
return { text: [], imgs: [] }
}
}
export const getFieldsFromPost = (record: PostRecord): RecordFields => {
const text: string[] = []
const imgs: CID[] = []
text.push(record.text)
const embeds = separateEmbeds(record.embed)
for (const embed of embeds) {
if (isEmbedImage(embed)) {
for (const img of embed.images) {
imgs.push(img.image.ref)
text.push(img.alt)
}
} else if (isEmbedExternal(embed)) {
if (embed.external.thumb) {
imgs.push(embed.external.thumb.ref)
}
text.push(embed.external.title)
text.push(embed.external.description)
}
}
return { text, imgs }
}
export const getFieldsFromProfile = (record: ProfileRecord): RecordFields => {
const text: string[] = []
const imgs: CID[] = []
if (record.displayName) {
text.push(record.displayName)
}
if (record.description) {
text.push(record.description)
}
if (record.avatar) {
imgs.push(record.avatar.ref)
}
if (record.banner) {
imgs.push(record.banner.ref)
}
return { text, imgs }
}
export const dedupe = (str: string[]): string[] => {
const set = new Set(str)
return [...set]
}
export const isPost = (obj: unknown): obj is PostRecord => {
return isRecordType(obj, 'app.bsky.feed.post')
}
export const isProfile = (obj: unknown): obj is ProfileRecord => {
return isRecordType(obj, 'app.bsky.actor.profile')
}
export const isRecordType = (obj: unknown, lexId: string): boolean => {
try {
lex.lexicons.assertValidRecord(lexId, obj)
return true
} catch {
return false
}
}
export const keywordLabeling = (
keywords: Record<string, string>,
text: string,
): string[] => {
const lowerText = text.toLowerCase()
const labels: string[] = []
for (const word of Object.keys(keywords)) {
if (lowerText.includes(word)) {
labels.push(keywords[word])
}
}
return labels
}
const separateEmbeds = (embed: PostRecord['embed']) => {
if (!embed) {
return []
}
if (isEmbedRecordWithMedia(embed)) {
return [{ $type: lex.ids.AppBskyEmbedRecord, ...embed.record }, embed.media]
}
return [embed]
}

@ -1113,44 +1113,40 @@ export const schemaDict = {
label: {
type: 'object',
description: 'Metadata tag on an atproto resource (eg, repo or record)',
key: 'tid',
record: {
type: 'object',
required: ['src', 'uri', 'val', 'cts'],
properties: {
src: {
type: 'string',
format: 'did',
description: 'DID of the actor who created this label',
},
uri: {
type: 'string',
format: 'uri',
description:
'AT URI of the record, repository (account), or other resource which this label applies to',
},
cid: {
type: 'string',
format: 'cid',
description:
"optionally, CID specifying the specific version of 'uri' resource this label applies to",
},
val: {
type: 'string',
maxLength: 128,
description:
'the short string name of the value or type of this label',
},
neg: {
type: 'boolean',
description:
'if true, this is a negation label, overwriting a previous label',
},
cts: {
type: 'string',
format: 'datetime',
description: 'timestamp when this label was created',
},
required: ['src', 'uri', 'val', 'cts'],
properties: {
src: {
type: 'string',
format: 'did',
description: 'DID of the actor who created this label',
},
uri: {
type: 'string',
format: 'uri',
description:
'AT URI of the record, repository (account), or other resource which this label applies to',
},
cid: {
type: 'string',
format: 'cid',
description:
"optionally, CID specifying the specific version of 'uri' resource this label applies to",
},
val: {
type: 'string',
maxLength: 128,
description:
'the short string name of the value or type of this label',
},
neg: {
type: 'boolean',
description:
'if true, this is a negation label, overwriting a previous label',
},
cts: {
type: 'string',
format: 'datetime',
description: 'timestamp when this label was created',
},
},
},

@ -7,7 +7,21 @@ import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
/** Metadata tag on an atproto resource (eg, repo or record) */
export interface Label {}
export interface Label {
/** DID of the actor who created this label */
src: string
/** AT URI of the record, repository (account), or other resource which this label applies to */
uri: string
/** optionally, CID specifying the specific version of 'uri' resource this label applies to */
cid?: string
/** the short string name of the value or type of this label */
val: string
/** if true, this is a negation label, overwriting a previous label */
neg?: boolean
/** timestamp when this label was created */
cts: string
[k: string]: unknown
}
export function isLabel(v: unknown): v is Label {
return (

@ -7,6 +7,7 @@ import { parseBasicAuth } from './auth'
export const dbLogger = subsystemLogger('pds:db')
export const seqLogger = subsystemLogger('pds:sequencer')
export const mailerLogger = subsystemLogger('pds:mailer')
export const labelerLogger = subsystemLogger('pds:labler')
export const httpLogger = subsystemLogger('pds')
export const loggerMiddleware = pinoHttp({

@ -12,6 +12,8 @@ import { ModerationService } from './moderation'
import { ActorService } from '../app-view/services/actor'
import { FeedService } from '../app-view/services/feed'
import { IndexingService } from '../app-view/services/indexing'
import { Labeler } from '../labeler'
import { LabelService } from '../app-view/services/label'
export function createServices(resources: {
repoSigningKey: crypto.Keypair
@ -19,6 +21,7 @@ export function createServices(resources: {
blobstore: BlobStore
imgUriBuilder: ImageUriBuilder
imgInvalidator: ImageInvalidator
labeler: Labeler
}): Services {
const {
repoSigningKey,
@ -26,6 +29,7 @@ export function createServices(resources: {
blobstore,
imgUriBuilder,
imgInvalidator,
labeler,
} = resources
return {
account: AccountService.creator(),
@ -41,7 +45,8 @@ export function createServices(resources: {
appView: {
actor: ActorService.creator(imgUriBuilder),
feed: FeedService.creator(imgUriBuilder),
indexing: IndexingService.creator(messageDispatcher),
indexing: IndexingService.creator(labeler, messageDispatcher),
label: LabelService.creator(),
},
}
}
@ -56,6 +61,7 @@ export type Services = {
feed: FromDb<FeedService>
indexing: FromDb<IndexingService>
actor: FromDb<ActorService>
label: FromDb<LabelService>
}
}

@ -16,6 +16,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -62,6 +63,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -128,6 +130,7 @@ Object {
"followsCount": 0,
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 0,
"viewer": Object {
"muted": false,
@ -143,6 +146,7 @@ Object {
"followsCount": 0,
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 0,
"viewer": Object {
"muted": false,
@ -156,6 +160,7 @@ Object {
"followersCount": 0,
"followsCount": 0,
"handle": "dan.test",
"labels": Array [],
"postsCount": 0,
"viewer": Object {
"muted": false,

@ -91,6 +91,8 @@ export const runTestServer = async (
dbPostgresUrl: process.env.DB_POSTGRES_URL,
blobstoreLocation: `${blobstoreLoc}/blobs`,
blobstoreTmp: `${blobstoreLoc}/tmp`,
labelerDid: 'did:example:labeler',
labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' },
maxSubscriptionBuffer: 200,
repoBackfillLimitMs: HOUR,
...params,

@ -15,6 +15,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -154,6 +155,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -213,6 +215,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -259,6 +262,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -289,7 +310,7 @@ Array [
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -308,6 +329,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -334,6 +356,7 @@ Array [
},
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -368,6 +391,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -391,6 +415,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -429,6 +454,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -459,7 +502,7 @@ Array [
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -479,6 +522,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -502,6 +546,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -632,6 +677,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(9)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(12)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -643,7 +698,7 @@ Array [
"uri": "record(2)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -666,6 +721,7 @@ Array [
},
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -691,6 +747,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -818,6 +875,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -863,6 +921,7 @@ Array [
},
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -931,6 +990,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -996,6 +1056,7 @@ Array [
},
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1021,6 +1082,7 @@ Array [
},
"cid": "cids(12)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",

@ -0,0 +1,401 @@
{
"id": "02122580-c37f-11ed-81d2-000000000000",
"code": 200,
"project_id": 12345,
"user_id": 12345,
"created_on": "2023-03-15T22:16:18.408Z",
"status": [
{
"status": {
"code": "0",
"message": "SUCCESS"
},
"response": {
"input": {
"id": "02122580-c37f-11ed-81d2-000000000000",
"charge": 0.003,
"model": "mod55_dense",
"model_version": 1,
"model_type": "CATEGORIZATION",
"created_on": "2023-03-15T22:16:18.136Z",
"media": {
"url": null,
"filename": "bafkreiam7k6mvkyuoybq4ynhljvj5xa75sdbhjbolzjf5j2udx7vj5gnsy",
"type": "PHOTO",
"mime_type": "jpeg",
"mimetype": "image/jpeg",
"width": 800,
"height": 800,
"num_frames": 1,
"duration": 0
},
"user_id": 12345,
"project_id": 12345,
"config_version": 1,
"config_tag": "default"
},
"output": [
{
"time": 0,
"classes": [
{
"class": "general_not_nsfw_not_suggestive",
"score": 0.9998097218132356
},
{
"class": "general_nsfw",
"score": 8.857344804177162e-05
},
{
"class": "general_suggestive",
"score": 0.00010170473872266839
},
{
"class": "no_female_underwear",
"score": 0.9999923079040384
},
{
"class": "yes_female_underwear",
"score": 7.692095961599136e-06
},
{
"class": "no_male_underwear",
"score": 0.9999984904867634
},
{
"class": "yes_male_underwear",
"score": 1.5095132367094679e-06
},
{
"class": "no_sex_toy",
"score": 0.9999970970762551
},
{
"class": "yes_sex_toy",
"score": 2.9029237450490604e-06
},
{
"class": "no_female_nudity",
"score": 0.9999739028909301
},
{
"class": "yes_female_nudity",
"score": 2.60971090699536e-05
},
{
"class": "no_male_nudity",
"score": 0.9999711373083747
},
{
"class": "yes_male_nudity",
"score": 2.8862691625255323e-05
},
{
"class": "no_female_swimwear",
"score": 0.9999917609899659
},
{
"class": "yes_female_swimwear",
"score": 8.239010034025379e-06
},
{
"class": "no_male_shirtless",
"score": 0.9999583350744331
},
{
"class": "yes_male_shirtless",
"score": 4.166492556688088e-05
},
{
"class": "no_text",
"score": 0.9958378716447616
},
{
"class": "text",
"score": 0.0041621283552384265
},
{
"class": "animated",
"score": 0.46755478950048235
},
{
"class": "hybrid",
"score": 0.0011440363434524984
},
{
"class": "natural",
"score": 0.5313011741560651
},
{
"class": "animated_gun",
"score": 2.0713000782979496e-05
},
{
"class": "gun_in_hand",
"score": 1.5844730446534659e-06
},
{
"class": "gun_not_in_hand",
"score": 1.0338973818006654e-06
},
{
"class": "no_gun",
"score": 0.9999766686287906
},
{
"class": "culinary_knife_in_hand",
"score": 3.8063500083369785e-06
},
{
"class": "culinary_knife_not_in_hand",
"score": 7.94057948996249e-07
},
{
"class": "knife_in_hand",
"score": 4.5578955723278505e-07
},
{
"class": "knife_not_in_hand",
"score": 3.842124714748908e-07
},
{
"class": "no_knife",
"score": 0.999994559590014
},
{
"class": "a_little_bloody",
"score": 2.1317745626539786e-07
},
{
"class": "no_blood",
"score": 0.9999793341236429
},
{
"class": "other_blood",
"score": 2.0322054269591763e-05
},
{
"class": "very_bloody",
"score": 1.306446309561673e-07
},
{
"class": "no_pills",
"score": 0.9999989592376954
},
{
"class": "yes_pills",
"score": 1.0407623044588633e-06
},
{
"class": "no_smoking",
"score": 0.9999939101969173
},
{
"class": "yes_smoking",
"score": 6.089803082758281e-06
},
{
"class": "illicit_injectables",
"score": 6.925695592003094e-07
},
{
"class": "medical_injectables",
"score": 8.587808234452378e-07
},
{
"class": "no_injectables",
"score": 0.9999984486496174
},
{
"class": "no_nazi",
"score": 0.9999987449628097
},
{
"class": "yes_nazi",
"score": 1.2550371902234279e-06
},
{
"class": "no_kkk",
"score": 0.999999762417549
},
{
"class": "yes_kkk",
"score": 2.3758245111050425e-07
},
{
"class": "no_middle_finger",
"score": 0.9999881515231847
},
{
"class": "yes_middle_finger",
"score": 1.184847681536747e-05
},
{
"class": "no_terrorist",
"score": 0.9999998870793229
},
{
"class": "yes_terrorist",
"score": 1.1292067715380635e-07
},
{
"class": "no_overlay_text",
"score": 0.9996453363440359
},
{
"class": "yes_overlay_text",
"score": 0.0003546636559640924
},
{
"class": "no_sexual_activity",
"score": 0.9999563580374798
},
{
"class": "yes_sexual_activity",
"score": 0.99,
"realScore": 4.364196252012032e-05
},
{
"class": "hanging",
"score": 3.6435135762510905e-07
},
{
"class": "no_hanging_no_noose",
"score": 0.9999980779196416
},
{
"class": "noose",
"score": 1.5577290007796094e-06
},
{
"class": "no_realistic_nsfw",
"score": 0.9999944341007805
},
{
"class": "yes_realistic_nsfw",
"score": 5.565899219571182e-06
},
{
"class": "animated_corpse",
"score": 5.276802046755426e-07
},
{
"class": "human_corpse",
"score": 2.5449360984211012e-08
},
{
"class": "no_corpse",
"score": 0.9999994468704343
},
{
"class": "no_self_harm",
"score": 0.9999994515625507
},
{
"class": "yes_self_harm",
"score": 5.484374493605692e-07
},
{
"class": "no_drawing",
"score": 0.9978276028816608
},
{
"class": "yes_drawing",
"score": 0.0021723971183392485
},
{
"class": "no_emaciated_body",
"score": 0.9999998146500432
},
{
"class": "yes_emaciated_body",
"score": 1.853499568724518e-07
},
{
"class": "no_child_present",
"score": 0.9999970498515446
},
{
"class": "yes_child_present",
"score": 2.950148455380443e-06
},
{
"class": "no_sexual_intent",
"score": 0.9999963861546292
},
{
"class": "yes_sexual_intent",
"score": 3.613845370766111e-06
},
{
"class": "animal_genitalia_and_human",
"score": 2.255472023465222e-08
},
{
"class": "animal_genitalia_only",
"score": 4.6783185199931176e-07
},
{
"class": "animated_animal_genitalia",
"score": 6.707857419436447e-07
},
{
"class": "no_animal_genitalia",
"score": 0.9999988388276858
},
{
"class": "no_gambling",
"score": 0.9999960939687145
},
{
"class": "yes_gambling",
"score": 3.906031285604864e-06
},
{
"class": "no_undressed",
"score": 0.99999923356218
},
{
"class": "yes_undressed",
"score": 7.664378199789045e-07
},
{
"class": "no_confederate",
"score": 0.9999925456900376
},
{
"class": "yes_confederate",
"score": 7.454309962453175e-06
},
{
"class": "animated_alcohol",
"score": 1.8109949948066074e-06
},
{
"class": "no_alcohol",
"score": 0.9999916620957963
},
{
"class": "yes_alcohol",
"score": 5.88781463445443e-06
},
{
"class": "yes_drinking_alcohol",
"score": 6.390945746578106e-07
},
{
"class": "no_religious_icon",
"score": 0.9999862158580689
},
{
"class": "yes_religious_icon",
"score": 1.3784141931119298e-05
}
]
}
]
}
}
],
"from_cache": false
}

@ -0,0 +1,16 @@
import fs from 'fs/promises'
import * as hive from '../../src/labeler/hive'
describe('labeling', () => {
it('correctly parses hive responses', async () => {
const exampleRespBytes = await fs.readFile(
'tests/labeler/fixtures/hiveai_resp_example.json',
)
const exmapleResp = JSON.parse(exampleRespBytes.toString())
const classes = hive.respToClasses(exmapleResp)
expect(classes.length).toBeGreaterThan(10)
const labels = hive.summarizeLabels(classes)
expect(labels).toEqual(['porn'])
})
})

@ -0,0 +1,171 @@
import { AtUri, BlobRef } from '@atproto/api'
import stream from 'stream'
import { runTestServer, CloseFn } from '../_util'
import { Labeler } from '../../src/labeler'
import { Database } from '../../src'
import { BlobStore, cidForRecord } from '@atproto/repo'
import { keywordLabeling } from '../../src/labeler/util'
import { cidForCbor, streamToBytes, TID } from '@atproto/common'
import * as ui8 from 'uint8arrays'
describe('labeler', () => {
let close: CloseFn
let labeler: Labeler
let blobstore: BlobStore
let db: Database
let labelerDid: string
let badBlob1: BlobRef
let badBlob2: BlobRef
let goodBlob: BlobRef
beforeAll(async () => {
const server = await runTestServer({
dbPostgresSchema: 'views_author_feed',
})
close = server.close
blobstore = server.ctx.blobstore
db = server.ctx.db
labelerDid = server.ctx.cfg.labelerDid
labeler = new TestLabeler({
db,
blobstore,
labelerDid,
keywords: { label_me: 'test-label', another_label: 'another-label' },
})
const bytes1 = new Uint8Array([1, 2, 3, 4])
const bytes2 = new Uint8Array([5, 6, 7, 8])
const bytes3 = new Uint8Array([4, 3, 2, 1])
const cid1 = await cidForCbor(bytes1)
const cid2 = await cidForCbor(bytes2)
const cid3 = await cidForCbor(bytes3)
blobstore.putPermanent(cid1, bytes1)
blobstore.putPermanent(cid2, bytes2)
blobstore.putPermanent(cid3, bytes3)
badBlob1 = new BlobRef(cid1, 'image/jpeg', 4)
badBlob2 = new BlobRef(cid2, 'image/jpeg', 4)
goodBlob = new BlobRef(cid3, 'image/jpeg', 4)
})
afterAll(async () => {
await close()
})
const getLabels = async (uri: AtUri) => {
return await db.db
.selectFrom('label')
.where('subjectUri', '=', uri.toString())
.selectAll()
.execute()
}
it('labels text in posts', async () => {
const post = {
$type: 'app.bsky.feed.post',
text: 'blah blah label_me',
createdAt: new Date().toISOString(),
}
const cid = await cidForRecord(post)
const uri = postUri()
labeler.processRecord(uri, post)
await labeler.processAll()
const labels = await getLabels(uri)
expect(labels.length).toBe(1)
expect(labels[0]).toMatchObject({
sourceDid: labelerDid,
subjectUri: uri.toString(),
subjectCid: cid.toString(),
value: 'test-label',
negated: 0,
})
})
it('labels embeds in posts', async () => {
const post = {
$type: 'app.bsky.feed.post',
text: 'blah blah',
embed: {
$type: 'app.bsky.embed.images',
images: [
{
image: badBlob1,
alt: 'img',
},
{
image: badBlob2,
alt: 'another_label',
},
{
image: goodBlob,
alt: 'img',
},
],
},
createdAt: new Date().toISOString(),
}
const uri = postUri()
labeler.processRecord(uri, post)
await labeler.processAll()
const dbLabels = await getLabels(uri)
const labels = dbLabels.map((row) => row.value).sort()
expect(labels).toEqual(
['another-label', 'img-label', 'other-img-label'].sort(),
)
})
it('labels text & imgs in profiles', async () => {
const profile = {
$type: 'app.bsky.actor.profile',
displayName: 'label_me',
description: 'another_label',
avatar: badBlob1,
banner: badBlob2,
createdAt: new Date().toISOString(),
}
const uri = profileUri()
labeler.processRecord(uri, profile)
await labeler.processAll()
const dbLabels = await getLabels(uri)
const labels = dbLabels.map((row) => row.value).sort()
expect(labels).toEqual(
['test-label', 'another-label', 'img-label', 'other-img-label'].sort(),
)
})
})
const postUri = () =>
AtUri.make('did:example:alice', 'app.bsky.feed.post', TID.nextStr())
const profileUri = () =>
AtUri.make('did:example:alice', 'app.bsky.actor.profile', TID.nextStr())
class TestLabeler extends Labeler {
hiveApiKey: string
keywords: Record<string, string>
constructor(opts: {
db: Database
blobstore: BlobStore
labelerDid: string
keywords: Record<string, string>
}) {
const { db, blobstore, labelerDid, keywords } = opts
super({ db, blobstore, labelerDid })
this.keywords = keywords
}
async labelText(text: string): Promise<string[]> {
return keywordLabeling(this.keywords, text)
}
async labelImg(img: stream.Readable): Promise<string[]> {
const buf = await streamToBytes(img)
if (ui8.equals(buf, new Uint8Array([1, 2, 3, 4]))) {
return ['img-label']
}
if (ui8.equals(buf, new Uint8Array([5, 6, 7, 8]))) {
return ['other-img-label']
}
return []
}
}

@ -103,7 +103,7 @@ export default async (sc: SeedClient) => {
}
export const posts = {
alice: ['hey there', 'again', 'yoohoo'],
alice: ['hey there', 'again', 'yoohoo label_me'],
bob: ['bob back at it again!', 'bobby boy here', 'yoohoo'],
carol: ['hi im carol'],
dan: ['dan here!', '@alice.bluesky.xyz is the best'],
@ -111,6 +111,6 @@ export const posts = {
export const replies = {
alice: ['thanks bob'],
bob: ['hear that'],
bob: ['hear that label_me label_me_2'],
carol: ['of course'],
}

@ -15,6 +15,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -61,6 +62,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(2)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(2)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -91,7 +110,7 @@ Array [
"uri": "record(1)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -110,6 +129,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -240,6 +260,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(5)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -251,7 +281,7 @@ Array [
"uri": "record(6)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -272,6 +302,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -297,6 +328,7 @@ Array [
},
"cid": "cids(9)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -337,6 +369,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(0)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(0)",
"val": "test-label",
},
Object {
"cid": "cids(0)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(0)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -367,7 +417,7 @@ Array [
"uri": "record(1)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -389,6 +439,7 @@ Array [
},
"cid": "cids(2)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -416,6 +467,7 @@ Array [
},
"cid": "cids(2)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -444,6 +496,7 @@ Array [
},
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -469,6 +522,7 @@ Array [
},
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -597,6 +651,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -654,6 +709,7 @@ Array [
},
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -690,6 +746,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -717,6 +774,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -785,6 +843,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -852,6 +911,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -989,6 +1049,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1033,6 +1094,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1162,6 +1224,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1207,6 +1270,7 @@ Array [
},
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1239,6 +1303,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1284,6 +1349,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1314,7 +1397,7 @@ Array [
"uri": "record(3)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -1335,6 +1418,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1466,6 +1550,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(7)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1477,7 +1571,7 @@ Array [
"uri": "record(8)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -1502,6 +1596,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1531,6 +1626,7 @@ Array [
},
"cid": "cids(9)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",

@ -11,6 +11,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -24,6 +25,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -37,6 +39,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -51,6 +54,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -69,6 +73,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -82,6 +87,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -95,6 +101,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -109,6 +116,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -127,6 +135,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -140,6 +149,7 @@ Object {
"displayName": "display-dan",
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -153,6 +163,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -166,6 +177,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(7)",
"following": "record(6)",
@ -180,6 +192,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -198,6 +211,7 @@ Object {
"displayName": "display-dan",
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -211,6 +225,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -223,6 +238,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -243,6 +259,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -256,6 +273,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -269,6 +287,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -281,6 +300,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -301,6 +321,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -313,6 +334,7 @@ Object {
"displayName": "display-dan",
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -333,6 +355,7 @@ Object {
"displayName": "display-dan",
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -346,6 +369,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -358,6 +382,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -378,6 +403,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -391,6 +417,7 @@ Object {
"displayName": "display-dan",
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -404,6 +431,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -417,6 +445,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(7)",
"following": "record(6)",
@ -431,6 +460,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -449,6 +479,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -462,6 +493,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -474,6 +506,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -494,6 +527,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -506,6 +540,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -526,6 +561,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -539,6 +575,7 @@ Object {
"displayName": "display-bob",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -552,6 +589,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -564,6 +602,7 @@ Object {
"displayName": "display-dan",
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",
@ -584,6 +623,7 @@ Object {
"displayName": "display-carol",
"handle": "carol.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -597,6 +637,7 @@ Object {
"displayName": "display-alice",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -609,6 +650,7 @@ Object {
"displayName": "display-eve",
"handle": "eve.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(1)",
"following": "record(0)",

@ -8,6 +8,7 @@ Object {
"actor": Object {
"did": "user(0)",
"handle": "eve.test",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -19,6 +20,7 @@ Object {
"actor": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(1)",
"muted": false,
@ -31,6 +33,7 @@ Object {
"actor": Object {
"did": "user(2)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"following": "record(2)",
@ -48,6 +51,7 @@ Object {
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(5)",
"following": "record(4)",
@ -70,6 +74,7 @@ Object {
"actor": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",

@ -8,6 +8,7 @@ Array [
"displayName": "Dr. Lowell DuBuque",
"handle": "elta48.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": true,
},
@ -18,6 +19,7 @@ Array [
"displayName": "Sally Funk",
"handle": "magnus53.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": true,
},
@ -25,6 +27,7 @@ Array [
Object {
"did": "user(2)",
"handle": "nicolas-krajcik10.test",
"labels": Array [],
"viewer": Object {
"muted": true,
},
@ -35,6 +38,7 @@ Array [
"displayName": "Patrick Sawayn",
"handle": "jeffrey-sawayn87.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": true,
},
@ -45,6 +49,7 @@ Array [
"displayName": "Kim Streich",
"handle": "adrienne49.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": true,
},
@ -55,6 +60,7 @@ Array [
"displayName": "Carlton Abernathy IV",
"handle": "aliya-hodkiewicz.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"muted": true,
},

@ -6,6 +6,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -15,6 +16,7 @@ Array [
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "reply",
"reasonSubject": "record(3)",
"record": Object {
@ -38,6 +40,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -46,6 +49,7 @@ Array [
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "repost",
"reasonSubject": "record(4)",
"record": Object {
@ -66,6 +70,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(9)",
"following": "record(8)",
@ -75,6 +80,24 @@ Array [
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(7)",
"val": "test-label",
},
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(7)",
"val": "test-label-2",
},
],
"reason": "reply",
"reasonSubject": "record(4)",
"record": Object {
@ -106,7 +129,7 @@ Array [
"uri": "record(4)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"uri": "record(7)",
},
@ -114,6 +137,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -122,6 +146,7 @@ Array [
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -138,6 +163,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -147,6 +173,7 @@ Array [
"cid": "cids(7)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(12)",
"record": Object {
@ -163,6 +190,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -172,6 +200,7 @@ Array [
"cid": "cids(9)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -192,6 +221,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(9)",
"following": "record(8)",
@ -201,6 +231,7 @@ Array [
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(12)",
"record": Object {
@ -221,6 +252,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(9)",
"following": "record(8)",
@ -230,6 +262,7 @@ Array [
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -250,6 +283,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(9)",
"following": "record(8)",
@ -259,6 +293,7 @@ Array [
"cid": "cids(12)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -271,6 +306,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -280,6 +316,7 @@ Array [
"cid": "cids(13)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -301,6 +338,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -310,6 +348,24 @@ Array [
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [
Object {
"cid": "cids(0)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(0)",
"val": "test-label",
},
Object {
"cid": "cids(0)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(0)",
"val": "test-label-2",
},
],
"reason": "reply",
"reasonSubject": "record(3)",
"record": Object {
@ -341,7 +397,7 @@ Array [
"uri": "record(3)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"uri": "record(0)",
},
@ -353,6 +409,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -362,6 +419,7 @@ Array [
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(5)",
"record": Object {
@ -382,6 +440,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -391,6 +450,7 @@ Array [
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(3)",
"record": Object {
@ -411,6 +471,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -420,6 +481,7 @@ Array [
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -437,6 +499,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -446,6 +509,7 @@ Array [
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "reply",
"reasonSubject": "record(3)",
"record": Object {
@ -469,6 +533,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -477,6 +542,7 @@ Array [
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "repost",
"reasonSubject": "record(4)",
"record": Object {
@ -493,6 +559,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -502,6 +569,7 @@ Array [
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "reply",
"reasonSubject": "record(4)",
"record": Object {
@ -529,6 +597,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -538,6 +607,24 @@ Array [
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [
Object {
"cid": "cids(5)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(8)",
"val": "test-label",
},
Object {
"cid": "cids(5)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(8)",
"val": "test-label-2",
},
],
"reason": "reply",
"reasonSubject": "record(4)",
"record": Object {
@ -569,7 +656,7 @@ Array [
"uri": "record(4)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"uri": "record(8)",
},
@ -577,6 +664,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -585,6 +673,7 @@ Array [
"cid": "cids(7)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -601,6 +690,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -610,6 +700,7 @@ Array [
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(13)",
"record": Object {
@ -626,6 +717,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -635,6 +727,7 @@ Array [
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -655,6 +748,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -664,6 +758,7 @@ Array [
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(13)",
"record": Object {
@ -684,6 +779,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -693,6 +789,7 @@ Array [
"cid": "cids(12)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -709,6 +806,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -717,6 +815,7 @@ Array [
"cid": "cids(13)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "mention",
"record": Object {
"$type": "app.bsky.feed.post",
@ -754,6 +853,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -763,6 +863,7 @@ Array [
"cid": "cids(15)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -775,6 +876,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -784,6 +886,7 @@ Array [
"cid": "cids(16)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": true,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -801,6 +904,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -810,6 +914,7 @@ Array [
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "reply",
"reasonSubject": "record(3)",
"record": Object {
@ -833,6 +938,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -841,6 +947,7 @@ Array [
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "repost",
"reasonSubject": "record(4)",
"record": Object {
@ -857,6 +964,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -866,6 +974,7 @@ Array [
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "reply",
"reasonSubject": "record(4)",
"record": Object {
@ -893,6 +1002,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -902,6 +1012,24 @@ Array [
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [
Object {
"cid": "cids(5)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(8)",
"val": "test-label",
},
Object {
"cid": "cids(5)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(8)",
"val": "test-label-2",
},
],
"reason": "reply",
"reasonSubject": "record(4)",
"record": Object {
@ -933,7 +1061,7 @@ Array [
"uri": "record(4)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"uri": "record(8)",
},
@ -941,6 +1069,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -949,6 +1078,7 @@ Array [
"cid": "cids(7)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -965,6 +1095,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -974,6 +1105,7 @@ Array [
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(13)",
"record": Object {
@ -990,6 +1122,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -999,6 +1132,7 @@ Array [
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -1019,6 +1153,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -1028,6 +1163,7 @@ Array [
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(13)",
"record": Object {
@ -1048,6 +1184,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -1057,6 +1194,7 @@ Array [
"cid": "cids(12)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "like",
"reasonSubject": "record(4)",
"record": Object {
@ -1073,6 +1211,7 @@ Array [
"author": Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(6)",
"muted": false,
@ -1081,6 +1220,7 @@ Array [
"cid": "cids(13)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "mention",
"record": Object {
"$type": "app.bsky.feed.post",
@ -1118,6 +1258,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(10)",
"following": "record(9)",
@ -1127,6 +1268,7 @@ Array [
"cid": "cids(15)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -1139,6 +1281,7 @@ Array [
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -1148,6 +1291,7 @@ Array [
"cid": "cids(16)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",
@ -1167,6 +1311,7 @@ Object {
"author": Object {
"did": "user(0)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -1174,6 +1319,7 @@ Object {
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "repost",
"reasonSubject": "record(1)",
"record": Object {
@ -1194,6 +1340,7 @@ Object {
"displayName": "ali",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"muted": false,
@ -1202,6 +1349,16 @@ Object {
"cid": "cids(2)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(2)",
"val": "test-label",
},
],
"reason": "quote",
"reasonSubject": "record(1)",
"record": Object {
@ -1214,7 +1371,7 @@ Object {
"uri": "record(1)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"uri": "record(2)",
},
@ -1226,6 +1383,7 @@ Object {
"displayName": "ali",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(3)",
"muted": false,
@ -1234,6 +1392,7 @@ Object {
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"isRead": false,
"labels": Array [],
"reason": "follow",
"record": Object {
"$type": "app.bsky.graph.follow",

@ -8,6 +8,7 @@ Object {
"followsCount": 1,
"handle": "dan.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 2,
"viewer": Object {
"muted": false,
@ -26,6 +27,7 @@ Array [
"followsCount": 3,
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 4,
"viewer": Object {
"followedBy": "record(1)",
@ -42,6 +44,7 @@ Array [
"followsCount": 2,
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 3,
"viewer": Object {
"muted": false,
@ -52,6 +55,7 @@ Array [
"followersCount": 2,
"followsCount": 1,
"handle": "carol.test",
"labels": Array [],
"postsCount": 2,
"viewer": Object {
"following": "record(2)",
@ -63,6 +67,7 @@ Array [
"followersCount": 1,
"followsCount": 1,
"handle": "dan.test",
"labels": Array [],
"postsCount": 2,
"viewer": Object {
"followedBy": "record(3)",
@ -82,6 +87,7 @@ Object {
"followsCount": 3,
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 4,
"viewer": Object {
"followedBy": "record(1)",
@ -97,6 +103,7 @@ Object {
"followersCount": 1,
"followsCount": 1,
"handle": "dan.test",
"labels": Array [],
"postsCount": 2,
"viewer": Object {
"followedBy": "record(0)",
@ -115,6 +122,7 @@ Object {
"followsCount": 3,
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 4,
"viewer": Object {
"muted": false,
@ -133,6 +141,7 @@ Object {
"followsCount": 3,
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 4,
"viewer": Object {
"muted": false,
@ -147,6 +156,7 @@ Object {
"followsCount": 3,
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 4,
"viewer": Object {
"muted": false,
@ -164,6 +174,7 @@ Object {
"followsCount": 3,
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"postsCount": 4,
"viewer": Object {
"muted": false,

@ -5,6 +5,7 @@ Array [
Object {
"did": "user(0)",
"handle": "eve.test",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -12,6 +13,7 @@ Array [
Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(0)",
"muted": false,
@ -20,6 +22,7 @@ Array [
Object {
"did": "user(2)",
"handle": "carol.test",
"labels": Array [],
"viewer": Object {
"followedBy": "record(2)",
"following": "record(1)",
@ -33,6 +36,7 @@ Array [
"displayName": "bobby",
"handle": "bob.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"viewer": Object {
"followedBy": "record(4)",
"following": "record(3)",
@ -47,6 +51,7 @@ Array [
Object {
"did": "user(0)",
"handle": "eve.test",
"labels": Array [],
"viewer": Object {
"muted": false,
},
@ -54,6 +59,7 @@ Array [
Object {
"did": "user(1)",
"handle": "dan.test",
"labels": Array [],
"viewer": Object {
"following": "record(0)",
"muted": false,

@ -22,6 +22,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -71,6 +72,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -115,6 +117,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -153,6 +156,24 @@ Object {
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(1)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label",
},
Object {
"cid": "cids(1)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -183,7 +204,7 @@ Object {
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -207,6 +228,7 @@ Object {
},
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -255,6 +277,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -293,6 +316,24 @@ Object {
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(1)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label",
},
Object {
"cid": "cids(1)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -323,7 +364,7 @@ Object {
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -357,6 +398,7 @@ Object {
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -394,6 +436,24 @@ Object {
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -424,7 +484,7 @@ Object {
"uri": "record(3)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -447,6 +507,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -491,6 +552,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -518,6 +580,7 @@ Object {
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -565,6 +628,24 @@ Object {
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -595,7 +676,7 @@ Object {
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -619,6 +700,7 @@ Object {
},
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -667,6 +749,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -694,6 +777,7 @@ Object {
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -740,6 +824,24 @@ Object {
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -770,7 +872,7 @@ Object {
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -799,6 +901,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -825,6 +928,7 @@ Object {
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -863,6 +967,7 @@ Object {
},
"cid": "cids(2)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -909,6 +1014,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -946,6 +1052,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -988,6 +1095,7 @@ Object {
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1015,6 +1123,7 @@ Object {
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1062,6 +1171,24 @@ Object {
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1092,7 +1219,7 @@ Object {
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -1116,6 +1243,7 @@ Object {
},
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",

@ -15,6 +15,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -50,6 +51,7 @@ Array [
},
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -84,6 +86,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -107,6 +110,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -140,6 +144,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -151,7 +165,7 @@ Array [
"uri": "record(7)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -172,6 +186,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -221,6 +236,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -284,6 +300,7 @@ Array [
},
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -314,6 +331,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -351,6 +369,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -386,6 +405,7 @@ Array [
},
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -420,6 +440,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -443,6 +464,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -476,6 +498,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(7)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -487,7 +519,7 @@ Array [
"uri": "record(8)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -510,6 +542,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -535,6 +568,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -559,6 +593,7 @@ Array [
},
"cid": "cids(7)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -627,6 +662,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -692,6 +728,7 @@ Array [
},
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -717,6 +754,7 @@ Array [
},
"cid": "cids(12)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -747,6 +785,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -886,6 +925,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -945,6 +985,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -991,6 +1032,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1021,7 +1080,7 @@ Array [
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -1040,6 +1099,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1066,6 +1126,7 @@ Array [
},
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1100,6 +1161,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1123,6 +1185,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1161,6 +1224,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(10)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1191,7 +1272,7 @@ Array [
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -1211,6 +1292,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1234,6 +1316,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1364,6 +1447,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(9)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(12)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1375,7 +1468,7 @@ Array [
"uri": "record(2)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -1398,6 +1491,7 @@ Array [
},
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1423,6 +1517,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1550,6 +1645,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1595,6 +1691,7 @@ Array [
},
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1663,6 +1760,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1728,6 +1826,7 @@ Array [
},
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1753,6 +1852,7 @@ Array [
},
"cid": "cids(12)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1882,6 +1982,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1942,6 +2043,7 @@ Array [
},
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -1986,6 +2088,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(9)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(9)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2016,7 +2136,7 @@ Array [
"uri": "record(8)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -2037,6 +2157,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2064,6 +2185,7 @@ Array [
},
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2100,6 +2222,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2127,6 +2250,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2165,6 +2289,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(9)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(9)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2195,7 +2337,7 @@ Array [
"uri": "record(8)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -2217,6 +2359,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2244,6 +2387,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2377,6 +2521,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(9)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(12)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2388,7 +2542,7 @@ Array [
"uri": "record(0)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -2411,6 +2565,7 @@ Array [
},
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2438,6 +2593,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2505,6 +2661,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2568,6 +2725,7 @@ Array [
},
"cid": "cids(4)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2595,6 +2753,7 @@ Array [
},
"cid": "cids(11)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2723,6 +2882,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2784,6 +2944,7 @@ Array [
},
"cid": "cids(5)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2829,6 +2990,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(9)",
"val": "test-label",
},
Object {
"cid": "cids(7)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(9)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2859,7 +3038,7 @@ Array [
"uri": "record(8)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -2880,6 +3059,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2906,6 +3086,7 @@ Array [
},
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2942,6 +3123,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -2969,6 +3151,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3100,6 +3283,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(9)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(12)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3111,7 +3304,7 @@ Array [
"uri": "record(0)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -3136,6 +3329,7 @@ Array [
},
"cid": "cids(6)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3203,6 +3397,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3266,6 +3461,7 @@ Array [
},
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3297,6 +3493,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3347,6 +3544,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(1)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label",
},
Object {
"cid": "cids(1)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(4)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3377,7 +3592,7 @@ Array [
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -3398,6 +3613,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3425,6 +3641,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3455,6 +3672,7 @@ Array [
},
"cid": "cids(3)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3578,6 +3796,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3622,6 +3841,7 @@ Array [
},
"cid": "cids(8)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3648,6 +3868,7 @@ Array [
},
"cid": "cids(7)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3678,6 +3899,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3715,6 +3937,7 @@ Array [
},
"cid": "cids(1)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3761,6 +3984,24 @@ Array [
],
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(3)",
"val": "test-label",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(3)",
"val": "test-label-2",
},
],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3791,7 +4032,7 @@ Array [
"uri": "record(0)",
},
},
"text": "hear that",
"text": "hear that label_me label_me_2",
},
"replyCount": 1,
"repostCount": 0,
@ -3810,6 +4051,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3940,6 +4182,16 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(4)",
"cts": "1970-01-01T00:00:00.000Z",
"neg": false,
"src": "did:example:labeler",
"uri": "record(6)",
"val": "test-label",
},
],
"likeCount": 2,
"record": Object {
"$type": "app.bsky.feed.post",
@ -3951,7 +4203,7 @@ Array [
"uri": "record(7)",
},
},
"text": "yoohoo",
"text": "yoohoo label_me",
},
"replyCount": 0,
"repostCount": 0,
@ -3972,6 +4224,7 @@ Array [
},
"cid": "cids(0)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 3,
"record": Object {
"$type": "app.bsky.feed.post",
@ -4099,6 +4352,7 @@ Array [
},
},
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -4144,6 +4398,7 @@ Array [
},
"cid": "cids(9)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",
@ -4169,6 +4424,7 @@ Array [
},
"cid": "cids(10)",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"record": Object {
"$type": "app.bsky.feed.post",

@ -230,6 +230,7 @@ const snapTypeaheadPg = [
did: 'user(0)',
handle: 'cara-wiegand69.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(1)',
@ -237,6 +238,7 @@ const snapTypeaheadPg = [
handle: 'eudora-dietrich4.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(2)',
@ -244,6 +246,7 @@ const snapTypeaheadPg = [
handle: 'shane-torphy52.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(3)',
@ -251,11 +254,13 @@ const snapTypeaheadPg = [
handle: 'aliya-hodkiewicz.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(4)',
handle: 'carlos6.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(5)',
@ -263,6 +268,7 @@ const snapTypeaheadPg = [
handle: 'carolina-mcdermott77.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(6)',
@ -270,6 +276,7 @@ const snapTypeaheadPg = [
handle: 'cayla-marquardt39.test',
avatar,
viewer: { muted: false },
labels: [],
},
]
@ -280,16 +287,19 @@ const snapTypeaheadSqlite = [
handle: 'aliya-hodkiewicz.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(1)',
handle: 'cara-wiegand69.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(2)',
handle: 'carlos6.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(3)',
@ -297,6 +307,7 @@ const snapTypeaheadSqlite = [
handle: 'carolina-mcdermott77.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(4)',
@ -304,6 +315,7 @@ const snapTypeaheadSqlite = [
handle: 'eudora-dietrich4.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(5)',
@ -311,6 +323,7 @@ const snapTypeaheadSqlite = [
handle: 'shane-torphy52.test',
avatar,
viewer: { muted: false },
labels: [],
},
]
@ -319,6 +332,7 @@ const snapSearchPg = [
did: 'user(0)',
handle: 'cara-wiegand69.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(1)',
@ -327,6 +341,7 @@ const snapSearchPg = [
handle: 'eudora-dietrich4.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(2)',
@ -335,6 +350,7 @@ const snapSearchPg = [
handle: 'shane-torphy52.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(3)',
@ -343,11 +359,13 @@ const snapSearchPg = [
handle: 'aliya-hodkiewicz.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(4)',
handle: 'carlos6.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(5)',
@ -356,6 +374,7 @@ const snapSearchPg = [
handle: 'carolina-mcdermott77.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(6)',
@ -364,6 +383,7 @@ const snapSearchPg = [
handle: 'cayla-marquardt39.test',
avatar,
viewer: { muted: false },
labels: [],
},
]
@ -375,16 +395,19 @@ const snapSearchSqlite = [
handle: 'aliya-hodkiewicz.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(1)',
handle: 'cara-wiegand69.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(2)',
handle: 'carlos6.test',
viewer: { muted: false },
labels: [],
},
{
did: 'user(3)',
@ -393,6 +416,7 @@ const snapSearchSqlite = [
handle: 'carolina-mcdermott77.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(4)',
@ -401,6 +425,7 @@ const snapSearchSqlite = [
handle: 'eudora-dietrich4.test',
avatar,
viewer: { muted: false },
labels: [],
},
{
did: 'user(5)',
@ -409,5 +434,6 @@ const snapSearchSqlite = [
handle: 'shane-torphy52.test',
avatar,
viewer: { muted: false },
labels: [],
},
]

@ -33,6 +33,7 @@ describe('pds author feed views', () => {
bob = sc.dids.bob
carol = sc.dids.carol
dan = sc.dids.dan
await server.ctx.labeler.processAll()
})
afterAll(async () => {

@ -33,6 +33,7 @@ describe('pds notification views', () => {
sc = new SeedClient(agent)
await basicSeed(sc)
alice = sc.dids.alice
await server.ctx.labeler.processAll()
})
afterAll(async () => {

@ -29,6 +29,7 @@ describe('pds thread views', () => {
alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
await server.ctx.labeler.processAll()
})
beforeAll(async () => {

@ -36,6 +36,7 @@ describe('timeline views', () => {
bob = sc.dids.bob
carol = sc.dids.carol
dan = sc.dids.dan
await server.ctx.labeler.processAll()
})
afterAll(async () => {

@ -7122,7 +7122,7 @@ forever-agent@~0.6.1:
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"