@atproto/bsky 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/auto-moderator/abyss.d.ts +5 -3
- package/dist/db/index.js +15 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20230920T213858047Z-add-tags-to-post.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/tables/post.d.ts +1 -0
- package/dist/index.js +111 -25
- package/dist/index.js.map +3 -3
- package/dist/lexicon/lexicons.d.ts +22 -0
- package/dist/lexicon/types/app/bsky/feed/post.d.ts +1 -0
- package/dist/lexicon/types/app/bsky/richtext/facet.d.ts +7 -1
- package/package.json +5 -5
- package/src/auto-moderator/abyss.ts +20 -12
- package/src/auto-moderator/index.ts +40 -5
- package/src/db/migrations/20230920T213858047Z-add-tags-to-post.ts +9 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/tables/post.ts +1 -0
- package/src/lexicon/lexicons.ts +23 -0
- package/src/lexicon/types/app/bsky/feed/post.ts +2 -0
- package/src/lexicon/types/app/bsky/richtext/facet.ts +17 -1
- package/src/services/actor/views.ts +16 -4
- package/src/services/feed/index.ts +1 -0
- package/src/services/indexing/plugins/post.ts +3 -0
- package/tests/auto-moderator/takedowns.test.ts +2 -1
- package/tests/views/__snapshots__/block-lists.test.ts.snap +0 -1
- package/tests/views/blocks.test.ts +20 -2
- package/tests/views/posts.test.ts +26 -2
- package/tests/views/suggested-follows.test.ts +1 -1
|
@@ -5272,6 +5272,16 @@ export declare const schemaDict: {
|
|
|
5272
5272
|
type: string;
|
|
5273
5273
|
refs: string[];
|
|
5274
5274
|
};
|
|
5275
|
+
tags: {
|
|
5276
|
+
type: string;
|
|
5277
|
+
maxLength: number;
|
|
5278
|
+
items: {
|
|
5279
|
+
type: string;
|
|
5280
|
+
maxLength: number;
|
|
5281
|
+
maxGraphemes: number;
|
|
5282
|
+
};
|
|
5283
|
+
description: string;
|
|
5284
|
+
};
|
|
5275
5285
|
createdAt: {
|
|
5276
5286
|
type: string;
|
|
5277
5287
|
format: string;
|
|
@@ -6408,6 +6418,18 @@ export declare const schemaDict: {
|
|
|
6408
6418
|
};
|
|
6409
6419
|
};
|
|
6410
6420
|
};
|
|
6421
|
+
tag: {
|
|
6422
|
+
type: string;
|
|
6423
|
+
description: string;
|
|
6424
|
+
required: string[];
|
|
6425
|
+
properties: {
|
|
6426
|
+
tag: {
|
|
6427
|
+
type: string;
|
|
6428
|
+
maxLength: number;
|
|
6429
|
+
maxGraphemes: number;
|
|
6430
|
+
};
|
|
6431
|
+
};
|
|
6432
|
+
};
|
|
6411
6433
|
byteSlice: {
|
|
6412
6434
|
type: string;
|
|
6413
6435
|
description: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ValidationResult } from '@atproto/lexicon';
|
|
2
2
|
export interface Main {
|
|
3
3
|
index: ByteSlice;
|
|
4
|
-
features: (Mention | Link | {
|
|
4
|
+
features: (Mention | Link | Tag | {
|
|
5
5
|
$type: string;
|
|
6
6
|
[k: string]: unknown;
|
|
7
7
|
})[];
|
|
@@ -21,6 +21,12 @@ export interface Link {
|
|
|
21
21
|
}
|
|
22
22
|
export declare function isLink(v: unknown): v is Link;
|
|
23
23
|
export declare function validateLink(v: unknown): ValidationResult;
|
|
24
|
+
export interface Tag {
|
|
25
|
+
tag: string;
|
|
26
|
+
[k: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
export declare function isTag(v: unknown): v is Tag;
|
|
29
|
+
export declare function validateTag(v: unknown): ValidationResult;
|
|
24
30
|
export interface ByteSlice {
|
|
25
31
|
byteStart: number;
|
|
26
32
|
byteEnd: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"sharp": "^0.31.2",
|
|
38
38
|
"typed-emitter": "^2.1.0",
|
|
39
39
|
"uint8arrays": "3.0.0",
|
|
40
|
-
"@atproto/api": "^0.6.
|
|
40
|
+
"@atproto/api": "^0.6.18",
|
|
41
41
|
"@atproto/common": "^0.3.0",
|
|
42
42
|
"@atproto/crypto": "^0.2.2",
|
|
43
43
|
"@atproto/syntax": "^0.1.1",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"@types/qs": "^6.9.7",
|
|
56
56
|
"@types/sharp": "^0.31.0",
|
|
57
57
|
"axios": "^0.27.2",
|
|
58
|
-
"@atproto/api": "^0.6.
|
|
59
|
-
"@atproto/dev-env": "^0.2.
|
|
58
|
+
"@atproto/api": "^0.6.18",
|
|
59
|
+
"@atproto/dev-env": "^0.2.9",
|
|
60
60
|
"@atproto/lex-cli": "^0.2.1",
|
|
61
|
-
"@atproto/pds": "^0.1.
|
|
61
|
+
"@atproto/pds": "^0.1.18",
|
|
62
62
|
"@atproto/xrpc": "^0.3.1"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import { CID } from 'multiformats/cid'
|
|
3
|
+
import { AtUri } from '@atproto/syntax'
|
|
3
4
|
import * as ui8 from 'uint8arrays'
|
|
4
5
|
import { resolveBlob } from '../api/blob-resolver'
|
|
5
6
|
import { retryHttp } from '../util/retry'
|
|
@@ -8,7 +9,7 @@ import { IdResolver } from '@atproto/identity'
|
|
|
8
9
|
import { labelerLogger as log } from '../logger'
|
|
9
10
|
|
|
10
11
|
export interface ImageFlagger {
|
|
11
|
-
scanImage(did: string, cid: CID): Promise<string[]>
|
|
12
|
+
scanImage(did: string, cid: CID, uri: AtUri): Promise<string[]>
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export class Abyss implements ImageFlagger {
|
|
@@ -22,11 +23,11 @@ export class Abyss implements ImageFlagger {
|
|
|
22
23
|
this.auth = basicAuth(this.password)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
async scanImage(did: string, cid: CID): Promise<string[]> {
|
|
26
|
+
async scanImage(did: string, cid: CID, uri: AtUri): Promise<string[]> {
|
|
26
27
|
const start = Date.now()
|
|
27
28
|
const res = await retryHttp(async () => {
|
|
28
29
|
try {
|
|
29
|
-
return await this.makeReq(did, cid)
|
|
30
|
+
return await this.makeReq(did, cid, uri)
|
|
30
31
|
} catch (err) {
|
|
31
32
|
log.warn({ err, did, cid: cid.toString() }, 'abyss request failed')
|
|
32
33
|
throw err
|
|
@@ -39,20 +40,24 @@ export class Abyss implements ImageFlagger {
|
|
|
39
40
|
return this.parseRes(res)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
async makeReq(did: string, cid: CID): Promise<ScannerResp> {
|
|
43
|
+
async makeReq(did: string, cid: CID, uri: AtUri): Promise<ScannerResp> {
|
|
43
44
|
const { stream, contentType } = await resolveBlob(
|
|
44
45
|
did,
|
|
45
46
|
cid,
|
|
46
47
|
this.ctx.db,
|
|
47
48
|
this.ctx.idResolver,
|
|
48
49
|
)
|
|
49
|
-
const { data } = await axios.post(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const { data } = await axios.post(
|
|
51
|
+
this.getReqUrl({ did, uri: uri.toString() }),
|
|
52
|
+
stream,
|
|
53
|
+
{
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': contentType,
|
|
56
|
+
authorization: this.auth,
|
|
57
|
+
},
|
|
58
|
+
timeout: 10000,
|
|
53
59
|
},
|
|
54
|
-
|
|
55
|
-
})
|
|
60
|
+
)
|
|
56
61
|
return data
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -69,8 +74,11 @@ export class Abyss implements ImageFlagger {
|
|
|
69
74
|
return labels
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
getReqUrl(params: { did: string }) {
|
|
73
|
-
|
|
77
|
+
getReqUrl(params: { did: string; uri: string }) {
|
|
78
|
+
const search = new URLSearchParams(params)
|
|
79
|
+
return `${
|
|
80
|
+
this.endpoint
|
|
81
|
+
}/xrpc/com.atproto.unspecced.scanBlob?${search.toString()}`
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
@@ -18,7 +18,10 @@ import { ImageUriBuilder } from '../image/uri'
|
|
|
18
18
|
import { ImageInvalidator } from '../image/invalidator'
|
|
19
19
|
import { Abyss } from './abyss'
|
|
20
20
|
import { FuzzyMatcher, TextFlagger } from './fuzzy-matcher'
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
REASONOTHER,
|
|
23
|
+
REASONVIOLATION,
|
|
24
|
+
} from '../lexicon/types/com/atproto/moderation/defs'
|
|
22
25
|
|
|
23
26
|
export class AutoModerator {
|
|
24
27
|
public pushAgent?: AtpAgent
|
|
@@ -172,7 +175,7 @@ export class AutoModerator {
|
|
|
172
175
|
async checkImgForTakedown(uri: AtUri, recordCid: CID, imgCids: CID[]) {
|
|
173
176
|
if (imgCids.length < 0) return
|
|
174
177
|
const results = await Promise.all(
|
|
175
|
-
imgCids.map((cid) => this.imageFlagger?.scanImage(uri.host, cid)),
|
|
178
|
+
imgCids.map((cid) => this.imageFlagger?.scanImage(uri.host, cid, uri)),
|
|
176
179
|
)
|
|
177
180
|
const takedownCids: CID[] = []
|
|
178
181
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -207,7 +210,39 @@ export class AutoModerator {
|
|
|
207
210
|
takedownCids: CID[],
|
|
208
211
|
labels: string[],
|
|
209
212
|
) {
|
|
210
|
-
const
|
|
213
|
+
const reportReason = `automated takedown (${labels.join(
|
|
214
|
+
', ',
|
|
215
|
+
)}). account needs review and possibly additional action`
|
|
216
|
+
const takedownReason = `automated takedown for labels: ${labels.join(', ')}`
|
|
217
|
+
log.warn(
|
|
218
|
+
{
|
|
219
|
+
uri: uri.toString(),
|
|
220
|
+
blobCids: takedownCids,
|
|
221
|
+
labels,
|
|
222
|
+
},
|
|
223
|
+
'hard takedown of record (and blobs) based on auto-matching',
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if (this.services.moderation) {
|
|
227
|
+
await this.ctx.db.transaction(async (dbTxn) => {
|
|
228
|
+
// directly/locally create report, even if we use pushAgent for the takedown. don't have acctual account credentials for pushAgent, only admin auth
|
|
229
|
+
if (!this.services.moderation) {
|
|
230
|
+
// checked above, outside the transaction
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
const modSrvc = this.services.moderation(dbTxn)
|
|
234
|
+
await modSrvc.report({
|
|
235
|
+
reportedBy: this.ctx.cfg.labelerDid,
|
|
236
|
+
reasonType: REASONVIOLATION,
|
|
237
|
+
subject: {
|
|
238
|
+
uri: uri,
|
|
239
|
+
cid: recordCid,
|
|
240
|
+
},
|
|
241
|
+
reason: reportReason,
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
if (this.pushAgent) {
|
|
212
247
|
await this.pushAgent.com.atproto.admin.takeModerationAction({
|
|
213
248
|
action: 'com.atproto.admin.defs#takedown',
|
|
@@ -217,7 +252,7 @@ export class AutoModerator {
|
|
|
217
252
|
cid: recordCid.toString(),
|
|
218
253
|
},
|
|
219
254
|
subjectBlobCids: takedownCids.map((c) => c.toString()),
|
|
220
|
-
reason,
|
|
255
|
+
reason: takedownReason,
|
|
221
256
|
createdBy: this.ctx.cfg.labelerDid,
|
|
222
257
|
})
|
|
223
258
|
} else {
|
|
@@ -230,7 +265,7 @@ export class AutoModerator {
|
|
|
230
265
|
action: 'com.atproto.admin.defs#takedown',
|
|
231
266
|
subject: { uri, cid: recordCid },
|
|
232
267
|
subjectBlobCids: takedownCids,
|
|
233
|
-
reason,
|
|
268
|
+
reason: takedownReason,
|
|
234
269
|
createdBy: this.ctx.cfg.labelerDid,
|
|
235
270
|
})
|
|
236
271
|
await modSrvc.takedownRecord({
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Kysely } from 'kysely'
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await db.schema.alterTable('post').addColumn('tags', 'jsonb').execute()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
8
|
+
await db.schema.alterTable('post').dropColumn('tags').execute()
|
|
9
|
+
}
|
|
@@ -28,3 +28,4 @@ export * as _20230817T195936007Z from './20230817T195936007Z-native-notification
|
|
|
28
28
|
export * as _20230830T205507322Z from './20230830T205507322Z-suggested-feeds'
|
|
29
29
|
export * as _20230904T211011773Z from './20230904T211011773Z-block-lists'
|
|
30
30
|
export * as _20230906T222220386Z from './20230906T222220386Z-thread-gating'
|
|
31
|
+
export * as _20230920T213858047Z from './20230920T213858047Z-add-tags-to-post'
|
package/src/db/tables/post.ts
CHANGED
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -5622,6 +5622,16 @@ export const schemaDict = {
|
|
|
5622
5622
|
type: 'union',
|
|
5623
5623
|
refs: ['lex:com.atproto.label.defs#selfLabels'],
|
|
5624
5624
|
},
|
|
5625
|
+
tags: {
|
|
5626
|
+
type: 'array',
|
|
5627
|
+
maxLength: 8,
|
|
5628
|
+
items: {
|
|
5629
|
+
type: 'string',
|
|
5630
|
+
maxLength: 640,
|
|
5631
|
+
maxGraphemes: 64,
|
|
5632
|
+
},
|
|
5633
|
+
description: 'Additional non-inline tags describing this post.',
|
|
5634
|
+
},
|
|
5625
5635
|
createdAt: {
|
|
5626
5636
|
type: 'string',
|
|
5627
5637
|
format: 'datetime',
|
|
@@ -6761,6 +6771,7 @@ export const schemaDict = {
|
|
|
6761
6771
|
refs: [
|
|
6762
6772
|
'lex:app.bsky.richtext.facet#mention',
|
|
6763
6773
|
'lex:app.bsky.richtext.facet#link',
|
|
6774
|
+
'lex:app.bsky.richtext.facet#tag',
|
|
6764
6775
|
],
|
|
6765
6776
|
},
|
|
6766
6777
|
},
|
|
@@ -6788,6 +6799,18 @@ export const schemaDict = {
|
|
|
6788
6799
|
},
|
|
6789
6800
|
},
|
|
6790
6801
|
},
|
|
6802
|
+
tag: {
|
|
6803
|
+
type: 'object',
|
|
6804
|
+
description: 'A hashtag.',
|
|
6805
|
+
required: ['tag'],
|
|
6806
|
+
properties: {
|
|
6807
|
+
tag: {
|
|
6808
|
+
type: 'string',
|
|
6809
|
+
maxLength: 640,
|
|
6810
|
+
maxGraphemes: 64,
|
|
6811
|
+
},
|
|
6812
|
+
},
|
|
6813
|
+
},
|
|
6791
6814
|
byteSlice: {
|
|
6792
6815
|
type: 'object',
|
|
6793
6816
|
description:
|
|
@@ -8,7 +8,7 @@ import { CID } from 'multiformats/cid'
|
|
|
8
8
|
|
|
9
9
|
export interface Main {
|
|
10
10
|
index: ByteSlice
|
|
11
|
-
features: (Mention | Link | { $type: string; [k: string]: unknown })[]
|
|
11
|
+
features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[]
|
|
12
12
|
[k: string]: unknown
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -61,6 +61,22 @@ export function validateLink(v: unknown): ValidationResult {
|
|
|
61
61
|
return lexicons.validate('app.bsky.richtext.facet#link', v)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** A hashtag. */
|
|
65
|
+
export interface Tag {
|
|
66
|
+
tag: string
|
|
67
|
+
[k: string]: unknown
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isTag(v: unknown): v is Tag {
|
|
71
|
+
return (
|
|
72
|
+
isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.richtext.facet#tag'
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function validateTag(v: unknown): ValidationResult {
|
|
77
|
+
return lexicons.validate('app.bsky.richtext.facet#tag', v)
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
/** A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings. */
|
|
65
81
|
export interface ByteSlice {
|
|
66
82
|
byteStart: number
|
|
@@ -194,8 +194,14 @@ export class ActorViews {
|
|
|
194
194
|
mutedByList,
|
|
195
195
|
blockedBy: !!bam.blockedBy([viewer, did]),
|
|
196
196
|
blocking: bam.blocking([viewer, did]) ?? undefined,
|
|
197
|
-
following:
|
|
198
|
-
|
|
197
|
+
following:
|
|
198
|
+
prof?.viewerFollowing && !bam.block([viewer, did])
|
|
199
|
+
? prof.viewerFollowing
|
|
200
|
+
: undefined,
|
|
201
|
+
followedBy:
|
|
202
|
+
prof?.viewerFollowedBy && !bam.block([viewer, did])
|
|
203
|
+
? prof.viewerFollowedBy
|
|
204
|
+
: undefined,
|
|
199
205
|
}
|
|
200
206
|
: undefined,
|
|
201
207
|
labels: [...actorLabels, ...selfLabels],
|
|
@@ -314,8 +320,14 @@ export class ActorViews {
|
|
|
314
320
|
mutedByList,
|
|
315
321
|
blockedBy: !!bam.blockedBy([viewer, did]),
|
|
316
322
|
blocking: bam.blocking([viewer, did]) ?? undefined,
|
|
317
|
-
following:
|
|
318
|
-
|
|
323
|
+
following:
|
|
324
|
+
prof?.viewerFollowing && !bam.block([viewer, did])
|
|
325
|
+
? prof.viewerFollowing
|
|
326
|
+
: undefined,
|
|
327
|
+
followedBy:
|
|
328
|
+
prof?.viewerFollowedBy && !bam.block([viewer, did])
|
|
329
|
+
? prof.viewerFollowedBy
|
|
330
|
+
: undefined,
|
|
319
331
|
}
|
|
320
332
|
: undefined,
|
|
321
333
|
labels: [...actorLabels, ...selfLabels],
|
|
@@ -76,6 +76,9 @@ const insertFn = async (
|
|
|
76
76
|
langs: obj.langs?.length
|
|
77
77
|
? sql<string[]>`${JSON.stringify(obj.langs)}` // sidesteps kysely's array serialization, which is non-jsonb
|
|
78
78
|
: null,
|
|
79
|
+
tags: obj.tags?.length
|
|
80
|
+
? sql<string[]>`${JSON.stringify(obj.tags)}` // sidesteps kysely's array serialization, which is non-jsonb
|
|
81
|
+
: null,
|
|
79
82
|
indexedAt: timestamp,
|
|
80
83
|
}
|
|
81
84
|
const [insertedPost] = await Promise.all([
|
|
@@ -7,6 +7,7 @@ import { TestNetwork } from '@atproto/dev-env'
|
|
|
7
7
|
import { ImageRef, SeedClient } from '../seeds/client'
|
|
8
8
|
import usersSeed from '../seeds/users'
|
|
9
9
|
import { CID } from 'multiformats/cid'
|
|
10
|
+
import { AtUri } from '@atproto/syntax'
|
|
10
11
|
import { ImageFlagger } from '../../src/auto-moderator/abyss'
|
|
11
12
|
import { ImageInvalidator } from '../../src/image/invalidator'
|
|
12
13
|
import { sha256 } from '@atproto/crypto'
|
|
@@ -157,7 +158,7 @@ class TestInvalidator implements ImageInvalidator {
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
class TestFlagger implements ImageFlagger {
|
|
160
|
-
async scanImage(_did: string, cid: CID): Promise<string[]> {
|
|
161
|
+
async scanImage(_did: string, cid: CID, _uri: AtUri): Promise<string[]> {
|
|
161
162
|
if (cid.equals(badCid1)) {
|
|
162
163
|
return ['kill-it']
|
|
163
164
|
} else if (cid.equals(badCid2)) {
|
|
@@ -191,17 +191,35 @@ describe('pds views with blocking', () => {
|
|
|
191
191
|
{ actor: dan },
|
|
192
192
|
{ headers: await network.serviceHeaders(carol) },
|
|
193
193
|
)
|
|
194
|
-
expect(resCarol.data.viewer?.blocking).toBeUndefined
|
|
194
|
+
expect(resCarol.data.viewer?.blocking).toBeUndefined()
|
|
195
195
|
expect(resCarol.data.viewer?.blockedBy).toBe(true)
|
|
196
196
|
|
|
197
197
|
const resDan = await agent.api.app.bsky.actor.getProfile(
|
|
198
198
|
{ actor: carol },
|
|
199
199
|
{ headers: await network.serviceHeaders(dan) },
|
|
200
200
|
)
|
|
201
|
-
expect(resDan.data.viewer?.blocking).toBeDefined
|
|
201
|
+
expect(resDan.data.viewer?.blocking).toBeDefined()
|
|
202
202
|
expect(resDan.data.viewer?.blockedBy).toBe(false)
|
|
203
203
|
})
|
|
204
204
|
|
|
205
|
+
it('unsets viewer follow state when blocked', async () => {
|
|
206
|
+
// there are follows between carol and dan
|
|
207
|
+
const { data: profile } = await agent.api.app.bsky.actor.getProfile(
|
|
208
|
+
{ actor: carol },
|
|
209
|
+
{ headers: await network.serviceHeaders(dan) },
|
|
210
|
+
)
|
|
211
|
+
expect(profile.viewer?.following).toBeUndefined()
|
|
212
|
+
expect(profile.viewer?.followedBy).toBeUndefined()
|
|
213
|
+
const { data: result } = await agent.api.app.bsky.graph.getBlocks(
|
|
214
|
+
{},
|
|
215
|
+
{ headers: await network.serviceHeaders(dan) },
|
|
216
|
+
)
|
|
217
|
+
const blocked = result.blocks.find((block) => block.did === carol)
|
|
218
|
+
expect(blocked).toBeDefined()
|
|
219
|
+
expect(blocked?.viewer?.following).toBeUndefined()
|
|
220
|
+
expect(blocked?.viewer?.followedBy).toBeUndefined()
|
|
221
|
+
})
|
|
222
|
+
|
|
205
223
|
it('returns block status on getProfiles', async () => {
|
|
206
224
|
const resCarol = await agent.api.app.bsky.actor.getProfiles(
|
|
207
225
|
{ actors: [alice, dan] },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import AtpAgent from '@atproto/api'
|
|
1
|
+
import AtpAgent, { AppBskyFeedPost } from '@atproto/api'
|
|
2
2
|
import { TestNetwork } from '@atproto/dev-env'
|
|
3
3
|
import { forSnapshot, stripViewerFromPost } from '../_util'
|
|
4
4
|
import { SeedClient } from '../seeds/client'
|
|
@@ -7,6 +7,7 @@ import basicSeed from '../seeds/basic'
|
|
|
7
7
|
describe('pds posts views', () => {
|
|
8
8
|
let network: TestNetwork
|
|
9
9
|
let agent: AtpAgent
|
|
10
|
+
let pdsAgent: AtpAgent
|
|
10
11
|
let sc: SeedClient
|
|
11
12
|
|
|
12
13
|
beforeAll(async () => {
|
|
@@ -14,7 +15,7 @@ describe('pds posts views', () => {
|
|
|
14
15
|
dbPostgresSchema: 'bsky_views_posts',
|
|
15
16
|
})
|
|
16
17
|
agent = network.bsky.getClient()
|
|
17
|
-
|
|
18
|
+
pdsAgent = network.pds.getClient()
|
|
18
19
|
sc = new SeedClient(pdsAgent)
|
|
19
20
|
await basicSeed(sc)
|
|
20
21
|
await network.processAll()
|
|
@@ -83,4 +84,27 @@ describe('pds posts views', () => {
|
|
|
83
84
|
].sort()
|
|
84
85
|
expect(receivedUris).toEqual(expected)
|
|
85
86
|
})
|
|
87
|
+
|
|
88
|
+
it('allows for creating posts with tags', async () => {
|
|
89
|
+
const post: AppBskyFeedPost.Record = {
|
|
90
|
+
text: 'hello world',
|
|
91
|
+
tags: ['javascript', 'hehe'],
|
|
92
|
+
createdAt: new Date().toISOString(),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { uri } = await pdsAgent.api.app.bsky.feed.post.create(
|
|
96
|
+
{ repo: sc.dids.alice },
|
|
97
|
+
post,
|
|
98
|
+
sc.getHeaders(sc.dids.alice),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
await network.processAll()
|
|
102
|
+
await network.bsky.processAll()
|
|
103
|
+
|
|
104
|
+
const { data } = await agent.api.app.bsky.feed.getPosts({ uris: [uri] })
|
|
105
|
+
|
|
106
|
+
expect(data.posts.length).toBe(1)
|
|
107
|
+
// @ts-ignore we know it's a post record
|
|
108
|
+
expect(data.posts[0].record.tags).toEqual(['javascript', 'hehe'])
|
|
109
|
+
})
|
|
86
110
|
})
|
|
@@ -11,7 +11,7 @@ describe('suggested follows', () => {
|
|
|
11
11
|
|
|
12
12
|
beforeAll(async () => {
|
|
13
13
|
network = await TestNetwork.create({
|
|
14
|
-
dbPostgresSchema: '
|
|
14
|
+
dbPostgresSchema: 'bsky_views_suggested_follows',
|
|
15
15
|
})
|
|
16
16
|
agent = network.bsky.getClient()
|
|
17
17
|
pdsAgent = network.pds.getClient()
|