@evanp/activitypub-bot 0.8.0
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/.github/dependabot.yml +11 -0
- package/.github/workflows/main-docker.yml +45 -0
- package/.github/workflows/tag-docker.yml +54 -0
- package/Dockerfile +23 -0
- package/LICENSE +661 -0
- package/README.md +82 -0
- package/bots/index.js +7 -0
- package/docs/activitypub.bot.drawio +110 -0
- package/index.js +23 -0
- package/lib/activitydistributor.js +263 -0
- package/lib/activityhandler.js +999 -0
- package/lib/activitypubclient.js +126 -0
- package/lib/activitystreams.js +41 -0
- package/lib/actorstorage.js +300 -0
- package/lib/app.js +173 -0
- package/lib/authorizer.js +133 -0
- package/lib/bot.js +44 -0
- package/lib/botcontext.js +520 -0
- package/lib/botdatastorage.js +87 -0
- package/lib/bots/donothing.js +11 -0
- package/lib/bots/ok.js +41 -0
- package/lib/digester.js +23 -0
- package/lib/httpsignature.js +195 -0
- package/lib/httpsignatureauthenticator.js +81 -0
- package/lib/keystorage.js +113 -0
- package/lib/microsyntax.js +140 -0
- package/lib/objectcache.js +48 -0
- package/lib/objectstorage.js +319 -0
- package/lib/remotekeystorage.js +116 -0
- package/lib/routes/collection.js +92 -0
- package/lib/routes/health.js +24 -0
- package/lib/routes/inbox.js +83 -0
- package/lib/routes/object.js +69 -0
- package/lib/routes/server.js +47 -0
- package/lib/routes/user.js +63 -0
- package/lib/routes/webfinger.js +36 -0
- package/lib/urlformatter.js +97 -0
- package/package.json +51 -0
- package/tests/activitydistributor.test.js +606 -0
- package/tests/activityhandler.test.js +2185 -0
- package/tests/activitypubclient.test.js +225 -0
- package/tests/actorstorage.test.js +261 -0
- package/tests/app.test.js +17 -0
- package/tests/authorizer.test.js +306 -0
- package/tests/bot.donothing.test.js +30 -0
- package/tests/bot.ok.test.js +101 -0
- package/tests/botcontext.test.js +674 -0
- package/tests/botdatastorage.test.js +87 -0
- package/tests/digester.test.js +56 -0
- package/tests/fixtures/bots.js +15 -0
- package/tests/httpsignature.test.js +200 -0
- package/tests/httpsignatureauthenticator.test.js +463 -0
- package/tests/keystorage.test.js +89 -0
- package/tests/microsyntax.test.js +122 -0
- package/tests/objectcache.test.js +133 -0
- package/tests/objectstorage.test.js +148 -0
- package/tests/remotekeystorage.test.js +76 -0
- package/tests/routes.actor.test.js +207 -0
- package/tests/routes.collection.test.js +434 -0
- package/tests/routes.health.test.js +41 -0
- package/tests/routes.inbox.test.js +135 -0
- package/tests/routes.object.test.js +519 -0
- package/tests/routes.server.test.js +69 -0
- package/tests/routes.webfinger.test.js +41 -0
- package/tests/urlformatter.test.js +164 -0
- package/tests/utils/digest.js +7 -0
- package/tests/utils/nock.js +276 -0
|
@@ -0,0 +1,999 @@
|
|
|
1
|
+
import as2 from './activitystreams.js'
|
|
2
|
+
import { nanoid } from 'nanoid'
|
|
3
|
+
|
|
4
|
+
const AS2 = 'https://www.w3.org/ns/activitystreams#'
|
|
5
|
+
|
|
6
|
+
const THREAD_PROP = 'https://purl.archive.org/socialweb/thread#thread'
|
|
7
|
+
|
|
8
|
+
export class ActivityHandler {
|
|
9
|
+
#actorStorage = null
|
|
10
|
+
#objectStorage = null
|
|
11
|
+
#distributor = null
|
|
12
|
+
#cache = null
|
|
13
|
+
#formatter = null
|
|
14
|
+
#authz = null
|
|
15
|
+
#logger = null
|
|
16
|
+
#client = null
|
|
17
|
+
constructor (
|
|
18
|
+
actorStorage,
|
|
19
|
+
objectStorage,
|
|
20
|
+
distributor,
|
|
21
|
+
formatter,
|
|
22
|
+
cache,
|
|
23
|
+
authz,
|
|
24
|
+
logger,
|
|
25
|
+
client
|
|
26
|
+
) {
|
|
27
|
+
this.#actorStorage = actorStorage
|
|
28
|
+
this.#objectStorage = objectStorage
|
|
29
|
+
this.#distributor = distributor
|
|
30
|
+
this.#formatter = formatter
|
|
31
|
+
this.#cache = cache
|
|
32
|
+
this.#authz = authz
|
|
33
|
+
this.#logger = logger.child({ class: this.constructor.name })
|
|
34
|
+
this.#client = client
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async handleActivity (bot, activity) {
|
|
38
|
+
switch (activity.type) {
|
|
39
|
+
case AS2 + 'Create': await this.#handleCreate(bot, activity); break
|
|
40
|
+
case AS2 + 'Update': await this.#handleUpdate(bot, activity); break
|
|
41
|
+
case AS2 + 'Delete': await this.#handleDelete(bot, activity); break
|
|
42
|
+
case AS2 + 'Add': await this.#handleAdd(bot, activity); break
|
|
43
|
+
case AS2 + 'Remove': await this.#handleRemove(bot, activity); break
|
|
44
|
+
case AS2 + 'Follow': await this.#handleFollow(bot, activity); break
|
|
45
|
+
case AS2 + 'Accept': await this.#handleAccept(bot, activity); break
|
|
46
|
+
case AS2 + 'Reject': await this.#handleReject(bot, activity); break
|
|
47
|
+
case AS2 + 'Like': await this.#handleLike(bot, activity); break
|
|
48
|
+
case AS2 + 'Announce': await this.#handleAnnounce(bot, activity); break
|
|
49
|
+
case AS2 + 'Undo': await this.#handleUndo(bot, activity); break
|
|
50
|
+
case AS2 + 'Block': await this.#handleBlock(bot, activity); break
|
|
51
|
+
case AS2 + 'Flag': await this.#handleFlag(bot, activity); break
|
|
52
|
+
default:
|
|
53
|
+
this.#logger.warn(`Unhandled activity type: ${activity.type}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async #handleCreate (bot, activity) {
|
|
58
|
+
const actor = this.#getActor(activity)
|
|
59
|
+
if (!actor) {
|
|
60
|
+
this.#logger.warn(
|
|
61
|
+
'Create activity has no actor',
|
|
62
|
+
{ activity: activity.id }
|
|
63
|
+
)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
const object = this.#getObject(activity)
|
|
67
|
+
if (!object) {
|
|
68
|
+
this.#logger.warn(
|
|
69
|
+
'Create activity has no object',
|
|
70
|
+
{ activity: activity.id }
|
|
71
|
+
)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
if (await this.#authz.sameOrigin(activity, object)) {
|
|
75
|
+
await this.#cache.save(object)
|
|
76
|
+
} else {
|
|
77
|
+
await this.#cache.saveReceived(object)
|
|
78
|
+
}
|
|
79
|
+
await this.#handleCreateReplies(bot, activity, actor, object)
|
|
80
|
+
await this.#handleCreateThread(bot, activity, actor, object)
|
|
81
|
+
if (this.#isMention(bot, object)) {
|
|
82
|
+
await bot.onMention(object, activity)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async #handleCreateReplies (bot, activity, actor, object) {
|
|
87
|
+
const inReplyTo = object.inReplyTo?.first
|
|
88
|
+
if (
|
|
89
|
+
inReplyTo &&
|
|
90
|
+
this.#formatter.isLocal(inReplyTo.id)
|
|
91
|
+
) {
|
|
92
|
+
let original = null
|
|
93
|
+
try {
|
|
94
|
+
original = await this.#objectStorage.read(inReplyTo.id)
|
|
95
|
+
} catch (err) {
|
|
96
|
+
this.#logger.warn(
|
|
97
|
+
'Create activity references not found original object',
|
|
98
|
+
{ activity: activity.id, original: inReplyTo.id }
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (this.#authz.isOwner(await this.#botActor(bot), original)) {
|
|
103
|
+
if (!await this.#authz.canRead(actor, original)) {
|
|
104
|
+
this.#logger.warn(
|
|
105
|
+
'Create activity references inaccessible original object',
|
|
106
|
+
{ activity: activity.id, original: original.id }
|
|
107
|
+
)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
if (await this.#objectStorage.isInCollection(original.id, 'replies', object)) {
|
|
111
|
+
this.#logger.warn(
|
|
112
|
+
'Create activity object already in replies collection',
|
|
113
|
+
{
|
|
114
|
+
activity: activity.id,
|
|
115
|
+
object: object.id,
|
|
116
|
+
original: original.id
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
await this.#objectStorage.addToCollection(
|
|
122
|
+
original.id,
|
|
123
|
+
'replies',
|
|
124
|
+
object
|
|
125
|
+
)
|
|
126
|
+
const recipients = this.#getRecipients(original)
|
|
127
|
+
this.#addRecipient(recipients, actor, 'to')
|
|
128
|
+
await this.#doActivity(bot, await as2.import({
|
|
129
|
+
type: 'Add',
|
|
130
|
+
id: this.#formatter.format({
|
|
131
|
+
username: bot.username,
|
|
132
|
+
type: 'add',
|
|
133
|
+
nanoid: nanoid()
|
|
134
|
+
}),
|
|
135
|
+
actor: original.actor,
|
|
136
|
+
object,
|
|
137
|
+
target: original.replies,
|
|
138
|
+
...recipients
|
|
139
|
+
}))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async #handleCreateThread (bot, activity, actor, object) {
|
|
145
|
+
const thread = await this.#objectThread(object)
|
|
146
|
+
if (!thread) {
|
|
147
|
+
this.#logger.warn(
|
|
148
|
+
'Object has no thread',
|
|
149
|
+
{ object: object.id }
|
|
150
|
+
)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
if (!this.#formatter.isLocal(thread.id)) {
|
|
154
|
+
this.#logger.warn(
|
|
155
|
+
'Not a local thread',
|
|
156
|
+
{ thread: thread.id }
|
|
157
|
+
)
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
const root = await this.#threadRoot(thread)
|
|
161
|
+
if (root && this.#formatter.isLocal(root.id)) {
|
|
162
|
+
if (this.#authz.isOwner(await this.#botActor(bot), root)) {
|
|
163
|
+
if (!await this.#authz.canRead(actor, root)) {
|
|
164
|
+
this.#logger.warn(
|
|
165
|
+
'Create activity references inaccessible root object',
|
|
166
|
+
{ activity: activity.id, root: root.id }
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
if (await this.#objectStorage.isInCollection(root.id, 'thread', object)) {
|
|
171
|
+
this.#logger.warn(
|
|
172
|
+
'Create activity object already in replies collection',
|
|
173
|
+
{
|
|
174
|
+
activity: activity.id,
|
|
175
|
+
object: object.id,
|
|
176
|
+
root: root.id
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
await this.#objectStorage.addToCollection(
|
|
182
|
+
root.id,
|
|
183
|
+
'thread',
|
|
184
|
+
object
|
|
185
|
+
)
|
|
186
|
+
const recipients = this.#getRecipients(root)
|
|
187
|
+
this.#addRecipient(recipients, actor, 'to')
|
|
188
|
+
await this.#doActivity(bot, await as2.import({
|
|
189
|
+
type: 'Add',
|
|
190
|
+
id: this.#formatter.format({
|
|
191
|
+
username: bot.username,
|
|
192
|
+
type: 'add',
|
|
193
|
+
nanoid: nanoid()
|
|
194
|
+
}),
|
|
195
|
+
actor: this.#botActor(bot),
|
|
196
|
+
object,
|
|
197
|
+
target: root.thread,
|
|
198
|
+
...recipients
|
|
199
|
+
}))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async #handleUpdate (bot, activity) {
|
|
205
|
+
const object = this.#getObject(activity)
|
|
206
|
+
if (await this.#authz.sameOrigin(activity, object)) {
|
|
207
|
+
await this.#cache.save(object)
|
|
208
|
+
} else {
|
|
209
|
+
await this.#cache.saveReceived(object)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async #handleDelete (bot, activity) {
|
|
214
|
+
const object = this.#getObject(activity)
|
|
215
|
+
await this.#cache.clear(object)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async #handleAdd (bot, activity) {
|
|
219
|
+
const actor = this.#getActor(activity)
|
|
220
|
+
const target = this.#getTarget(activity)
|
|
221
|
+
const object = this.#getObject(activity)
|
|
222
|
+
if (await this.#authz.sameOrigin(actor, object)) {
|
|
223
|
+
await this.#cache.save(object)
|
|
224
|
+
} else {
|
|
225
|
+
await this.#cache.saveReceived(object)
|
|
226
|
+
}
|
|
227
|
+
if (await this.#authz.sameOrigin(actor, target)) {
|
|
228
|
+
await this.#cache.save(target)
|
|
229
|
+
await this.#cache.saveMembership(target, object)
|
|
230
|
+
} else {
|
|
231
|
+
await this.#cache.saveReceived(target)
|
|
232
|
+
await this.#cache.saveMembershipReceived(target, object)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async #handleRemove (bot, activity) {
|
|
237
|
+
const actor = this.#getActor(activity)
|
|
238
|
+
const target = this.#getTarget(activity)
|
|
239
|
+
const object = this.#getObject(activity)
|
|
240
|
+
if (await this.#authz.sameOrigin(actor, object)) {
|
|
241
|
+
await this.#cache.save(object)
|
|
242
|
+
} else {
|
|
243
|
+
await this.#cache.saveReceived(object)
|
|
244
|
+
}
|
|
245
|
+
if (await this.#authz.sameOrigin(actor, target)) {
|
|
246
|
+
await this.#cache.save(target)
|
|
247
|
+
await this.#cache.saveMembership(target, object, false)
|
|
248
|
+
} else {
|
|
249
|
+
await this.#cache.saveReceived(target)
|
|
250
|
+
await this.#cache.saveMembershipReceived(target, object, false)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async #handleFollow (bot, activity) {
|
|
255
|
+
const actor = this.#getActor(activity)
|
|
256
|
+
const object = this.#getObject(activity)
|
|
257
|
+
if (object.id !== this.#botId(bot)) {
|
|
258
|
+
this.#logger.warn({
|
|
259
|
+
msg: 'Follow activity object is not the bot',
|
|
260
|
+
activity: activity.id,
|
|
261
|
+
object: object.id
|
|
262
|
+
})
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
if (await this.#actorStorage.isInCollection(bot.username, 'blocked', actor)) {
|
|
266
|
+
this.#logger.warn({
|
|
267
|
+
msg: 'Follow activity from blocked actor',
|
|
268
|
+
activity: activity.id,
|
|
269
|
+
actor: actor.id
|
|
270
|
+
})
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
if (await this.#actorStorage.isInCollection(bot.username, 'followers', actor)) {
|
|
274
|
+
this.#logger.warn({
|
|
275
|
+
msg: 'Duplicate follow activity',
|
|
276
|
+
activity: activity.id,
|
|
277
|
+
actor: actor.id
|
|
278
|
+
})
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
this.#logger.info({
|
|
282
|
+
msg: 'Adding follower',
|
|
283
|
+
actor: actor.id
|
|
284
|
+
})
|
|
285
|
+
await this.#actorStorage.addToCollection(bot.username, 'followers', actor)
|
|
286
|
+
this.#logger.info(
|
|
287
|
+
'Sending accept',
|
|
288
|
+
{ actor: actor.id }
|
|
289
|
+
)
|
|
290
|
+
const addActivityId = this.#formatter.format({
|
|
291
|
+
username: bot.username,
|
|
292
|
+
type: 'add',
|
|
293
|
+
nanoid: nanoid()
|
|
294
|
+
})
|
|
295
|
+
await this.#doActivity(bot, await as2.import({
|
|
296
|
+
id: addActivityId,
|
|
297
|
+
type: 'Add',
|
|
298
|
+
actor: this.#formatter.format({ username: bot.username }),
|
|
299
|
+
object: actor,
|
|
300
|
+
target: this.#formatter.format({
|
|
301
|
+
username: bot.username,
|
|
302
|
+
collection: 'followers'
|
|
303
|
+
}),
|
|
304
|
+
to: ['as:Public', actor.id]
|
|
305
|
+
}))
|
|
306
|
+
await this.#doActivity(bot, await as2.import({
|
|
307
|
+
id: this.#formatter.format({
|
|
308
|
+
username: bot.username,
|
|
309
|
+
type: 'accept',
|
|
310
|
+
nanoid: nanoid()
|
|
311
|
+
}),
|
|
312
|
+
type: 'Accept',
|
|
313
|
+
actor: this.#formatter.format({ username: bot.username }),
|
|
314
|
+
object: activity,
|
|
315
|
+
to: actor
|
|
316
|
+
}))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async #handleAccept (bot, activity) {
|
|
320
|
+
let objectActivity = this.#getObject(activity)
|
|
321
|
+
if (!this.#formatter.isLocal(objectActivity.id)) {
|
|
322
|
+
this.#logger.warn({ msg: 'Accept activity for a non-local activity' })
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
objectActivity = await this.#objectStorage.read(objectActivity.id)
|
|
327
|
+
} catch (err) {
|
|
328
|
+
this.#logger.warn({ msg: 'Accept activity object not found' })
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
switch (objectActivity.type) {
|
|
332
|
+
case AS2 + 'Follow':
|
|
333
|
+
await this.#handleAcceptFollow(bot, activity, objectActivity)
|
|
334
|
+
break
|
|
335
|
+
default:
|
|
336
|
+
console.log('Unhandled accept', objectActivity.type)
|
|
337
|
+
break
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async #handleAcceptFollow (bot, activity, followActivity) {
|
|
342
|
+
const actor = this.#getActor(activity)
|
|
343
|
+
if (
|
|
344
|
+
!(await this.#actorStorage.isInCollection(
|
|
345
|
+
bot.username,
|
|
346
|
+
'pendingFollowing',
|
|
347
|
+
followActivity
|
|
348
|
+
))
|
|
349
|
+
) {
|
|
350
|
+
this.#logger.warn({ msg: 'Accept activity object not found' })
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
if (await this.#actorStorage.isInCollection(bot.username, 'following', actor)) {
|
|
354
|
+
this.#logger.warn({ msg: 'Already following' })
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
if (await this.#actorStorage.isInCollection(bot.username, 'blocked', actor)) {
|
|
358
|
+
this.#logger.warn({ msg: 'blocked' })
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
const object = this.#getObject(followActivity)
|
|
362
|
+
if (object.id !== actor.id) {
|
|
363
|
+
this.#logger.warn({ msg: 'Object does not match actor' })
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
this.#logger.info({ msg: 'Adding to following' })
|
|
367
|
+
await this.#actorStorage.addToCollection(bot.username, 'following', actor)
|
|
368
|
+
await this.#actorStorage.removeFromCollection(
|
|
369
|
+
bot.username,
|
|
370
|
+
'pendingFollowing',
|
|
371
|
+
followActivity
|
|
372
|
+
)
|
|
373
|
+
await this.#doActivity(bot, await as2.import({
|
|
374
|
+
id: this.#formatter.format({
|
|
375
|
+
username: bot.username,
|
|
376
|
+
type: 'add',
|
|
377
|
+
nanoid: nanoid()
|
|
378
|
+
}),
|
|
379
|
+
type: 'Add',
|
|
380
|
+
actor: this.#formatter.format({ username: bot.username }),
|
|
381
|
+
object: actor,
|
|
382
|
+
target: this.#formatter.format({
|
|
383
|
+
username: bot.username,
|
|
384
|
+
collection: 'following'
|
|
385
|
+
}),
|
|
386
|
+
to: ['as:Public', actor.id]
|
|
387
|
+
}))
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async #handleReject (bot, activity) {
|
|
391
|
+
let objectActivity = this.#getObject(activity)
|
|
392
|
+
if (!this.#formatter.isLocal(objectActivity.id)) {
|
|
393
|
+
this.#logger.warn({ msg: 'Reject activity for a non-local activity' })
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
objectActivity = await this.#objectStorage.read(objectActivity.id)
|
|
398
|
+
} catch (err) {
|
|
399
|
+
this.#logger.warn({ msg: 'Reject activity object not found' })
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
switch (objectActivity.type) {
|
|
403
|
+
case AS2 + 'Follow':
|
|
404
|
+
await this.#handleRejectFollow(bot, activity, objectActivity)
|
|
405
|
+
break
|
|
406
|
+
default:
|
|
407
|
+
this.#logger.warn({ msg: 'Unhandled reject' })
|
|
408
|
+
break
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async #handleRejectFollow (bot, activity, followActivity) {
|
|
413
|
+
const actor = this.#getActor(activity)
|
|
414
|
+
if (
|
|
415
|
+
!(await this.#actorStorage.isInCollection(
|
|
416
|
+
bot.username,
|
|
417
|
+
'pendingFollowing',
|
|
418
|
+
followActivity
|
|
419
|
+
))
|
|
420
|
+
) {
|
|
421
|
+
this.#logger.warn({ msg: 'Reject activity object not found' })
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
if (await this.#actorStorage.isInCollection(bot.username, 'following', actor)) {
|
|
425
|
+
this.#logger.warn({ msg: 'Already following' })
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
if (await this.#actorStorage.isInCollection(bot.username, 'blocked', actor)) {
|
|
429
|
+
this.#logger.warn({ msg: 'blocked' })
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
const object = this.#getObject(followActivity)
|
|
433
|
+
if (object.id !== actor.id) {
|
|
434
|
+
this.#logger.warn({ msg: 'Object does not match actor' })
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
this.#logger.info({ msg: 'Removing from pending' })
|
|
438
|
+
await this.#actorStorage.removeFromCollection(
|
|
439
|
+
bot.username,
|
|
440
|
+
'pendingFollowing',
|
|
441
|
+
followActivity
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async #handleLike (bot, activity) {
|
|
446
|
+
const actor = this.#getActor(activity)
|
|
447
|
+
let object = this.#getObject(activity)
|
|
448
|
+
if (!this.#formatter.isLocal(object.id)) {
|
|
449
|
+
this.#logger.warn({
|
|
450
|
+
msg: 'Like activity object is not local',
|
|
451
|
+
activity: activity.id,
|
|
452
|
+
object: object.id
|
|
453
|
+
})
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
object = await this.#objectStorage.read(object.id)
|
|
458
|
+
} catch (err) {
|
|
459
|
+
this.#logger.warn({
|
|
460
|
+
msg: 'Like activity object not found',
|
|
461
|
+
activity: activity.id,
|
|
462
|
+
object: object.id
|
|
463
|
+
})
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
if (!(await this.#authz.canRead(actor, object))) {
|
|
467
|
+
this.#logger.warn({
|
|
468
|
+
msg: 'Like activity object is not readable',
|
|
469
|
+
activity: activity.id,
|
|
470
|
+
object: object.id
|
|
471
|
+
})
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
const owner = this.#getOwner(object)
|
|
475
|
+
if (!owner || owner.id !== this.#botId(bot)) {
|
|
476
|
+
this.#logger.warn({
|
|
477
|
+
msg: 'Like activity object is not owned by bot',
|
|
478
|
+
activity: activity.id,
|
|
479
|
+
object: object.id
|
|
480
|
+
})
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
if (await this.#objectStorage.isInCollection(object.id, 'likes', activity)) {
|
|
484
|
+
this.#logger.warn({
|
|
485
|
+
msg: 'Like activity already in likes collection',
|
|
486
|
+
activity: activity.id,
|
|
487
|
+
object: object.id
|
|
488
|
+
})
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
if (await this.#objectStorage.isInCollection(object.id, 'likers', actor)) {
|
|
492
|
+
this.#logger.warn({
|
|
493
|
+
msg: 'Actor already in likers collection',
|
|
494
|
+
activity: activity.id,
|
|
495
|
+
actor: actor.id,
|
|
496
|
+
object: object.id
|
|
497
|
+
})
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
await this.#objectStorage.addToCollection(object.id, 'likes', activity)
|
|
501
|
+
await this.#objectStorage.addToCollection(object.id, 'likers', actor)
|
|
502
|
+
const recipients = this.#getRecipients(object)
|
|
503
|
+
this.#addRecipient(recipients, actor, 'to')
|
|
504
|
+
await this.#doActivity(bot, await as2.import({
|
|
505
|
+
type: 'Add',
|
|
506
|
+
id: this.#formatter.format({
|
|
507
|
+
username: bot.username,
|
|
508
|
+
type: 'add',
|
|
509
|
+
nanoid: nanoid()
|
|
510
|
+
}),
|
|
511
|
+
actor: this.#botId(bot),
|
|
512
|
+
object: activity,
|
|
513
|
+
target: this.#formatter.format({
|
|
514
|
+
username: bot.username,
|
|
515
|
+
collection: 'likes'
|
|
516
|
+
}),
|
|
517
|
+
...recipients
|
|
518
|
+
}))
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async #handleAnnounce (bot, activity) {
|
|
522
|
+
const actor = this.#getActor(activity)
|
|
523
|
+
let object = this.#getObject(activity)
|
|
524
|
+
if (!this.#formatter.isLocal(object.id)) {
|
|
525
|
+
this.#logger.warn({
|
|
526
|
+
msg: 'Announce activity object is not local',
|
|
527
|
+
activity: activity.id,
|
|
528
|
+
object: object.id
|
|
529
|
+
})
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
object = await this.#objectStorage.read(object.id)
|
|
534
|
+
} catch (err) {
|
|
535
|
+
this.#logger.warn({
|
|
536
|
+
msg: 'Announce activity object not found',
|
|
537
|
+
activity: activity.id,
|
|
538
|
+
object: object.id
|
|
539
|
+
})
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
const owner = this.#getOwner(object)
|
|
543
|
+
if (!owner || owner.id !== this.#botId(bot)) {
|
|
544
|
+
this.#logger.warn({
|
|
545
|
+
msg: 'Announce activity object is not owned by bot',
|
|
546
|
+
activity: activity.id,
|
|
547
|
+
object: object.id
|
|
548
|
+
})
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
if (!(await this.#authz.canRead(actor, object))) {
|
|
552
|
+
this.#logger.warn({
|
|
553
|
+
msg: 'Announce activity object is not readable',
|
|
554
|
+
activity: activity.id,
|
|
555
|
+
object: object.id
|
|
556
|
+
})
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
if (await this.#objectStorage.isInCollection(object.id, 'shares', activity)) {
|
|
560
|
+
this.#logger.warn({
|
|
561
|
+
msg: 'Announce activity already in shares collection',
|
|
562
|
+
activity: activity.id,
|
|
563
|
+
object: object.id
|
|
564
|
+
})
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
if (await this.#objectStorage.isInCollection(object.id, 'sharers', actor)) {
|
|
568
|
+
this.#logger.warn({
|
|
569
|
+
msg: 'Actor already in sharers collection',
|
|
570
|
+
activity: activity.id,
|
|
571
|
+
actor: actor.id,
|
|
572
|
+
object: object.id
|
|
573
|
+
})
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
await this.#objectStorage.addToCollection(object.id, 'shares', activity)
|
|
577
|
+
await this.#objectStorage.addToCollection(object.id, 'sharers', actor)
|
|
578
|
+
const recipients = this.#getRecipients(object)
|
|
579
|
+
this.#addRecipient(recipients, actor, 'to')
|
|
580
|
+
await this.#doActivity(bot, await as2.import({
|
|
581
|
+
type: 'Add',
|
|
582
|
+
id: this.#formatter.format({
|
|
583
|
+
username: bot.username,
|
|
584
|
+
type: 'add',
|
|
585
|
+
nanoid: nanoid()
|
|
586
|
+
}),
|
|
587
|
+
actor: this.#botId(bot),
|
|
588
|
+
object: activity,
|
|
589
|
+
target: this.#formatter.format({
|
|
590
|
+
username: bot.username,
|
|
591
|
+
collection: 'shares'
|
|
592
|
+
}),
|
|
593
|
+
...recipients
|
|
594
|
+
}))
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async #handleBlock (bot, activity) {
|
|
598
|
+
const actor = this.#getActor(activity)
|
|
599
|
+
const object = this.#getObject(activity)
|
|
600
|
+
if (object.id === this.#botId(bot)) {
|
|
601
|
+
// These skip if not found
|
|
602
|
+
await this.#actorStorage.removeFromCollection(
|
|
603
|
+
bot.username,
|
|
604
|
+
'followers',
|
|
605
|
+
actor
|
|
606
|
+
)
|
|
607
|
+
await this.#actorStorage.removeFromCollection(
|
|
608
|
+
bot.username,
|
|
609
|
+
'following',
|
|
610
|
+
actor
|
|
611
|
+
)
|
|
612
|
+
await this.#actorStorage.removeFromCollection(
|
|
613
|
+
bot.username,
|
|
614
|
+
'pendingFollowing',
|
|
615
|
+
actor
|
|
616
|
+
)
|
|
617
|
+
await this.#actorStorage.removeFromCollection(
|
|
618
|
+
bot.username,
|
|
619
|
+
'pendingFollowers',
|
|
620
|
+
actor
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async #handleFlag (bot, activity) {
|
|
626
|
+
const actor = this.#getActor(activity)
|
|
627
|
+
const object = this.#getObject(activity)
|
|
628
|
+
this.#logger.warn(`Actor ${actor.id} flagged object ${object.id} for review.`)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async #handleUndo (bot, undoActivity) {
|
|
632
|
+
const undoActor = this.#getActor(undoActivity)
|
|
633
|
+
let activity = await this.#getObject(undoActivity)
|
|
634
|
+
if (!activity) {
|
|
635
|
+
this.#logger.warn({
|
|
636
|
+
msg: 'Undo activity has no object',
|
|
637
|
+
activity: undoActivity.id
|
|
638
|
+
})
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
activity = await this.#ensureProps(bot, undoActivity, activity, ['type'])
|
|
642
|
+
this.#logger.debug({ activityType: activity.type })
|
|
643
|
+
if (await this.#authz.sameOrigin(undoActivity, activity)) {
|
|
644
|
+
this.#logger.info({
|
|
645
|
+
msg: 'Assuming undo activity can undo an activity with same origin',
|
|
646
|
+
undoActivity: undoActivity.id,
|
|
647
|
+
activity: activity.id
|
|
648
|
+
})
|
|
649
|
+
} else {
|
|
650
|
+
activity = await this.#ensureProps(bot, undoActivity, activity, ['actor'])
|
|
651
|
+
const activityActor = this.#getActor(activity)
|
|
652
|
+
if (undoActor.id !== activityActor.id) {
|
|
653
|
+
this.#logger.warn({
|
|
654
|
+
msg: 'Undo activity actor does not match object activity actor',
|
|
655
|
+
activity: undoActivity.id,
|
|
656
|
+
object: activity.id
|
|
657
|
+
})
|
|
658
|
+
return
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
switch (activity.type) {
|
|
662
|
+
case AS2 + 'Like':
|
|
663
|
+
await this.#handleUndoLike(bot, undoActivity, activity)
|
|
664
|
+
break
|
|
665
|
+
case AS2 + 'Announce':
|
|
666
|
+
await this.#handleUndoAnnounce(bot, undoActivity, activity)
|
|
667
|
+
break
|
|
668
|
+
case AS2 + 'Block':
|
|
669
|
+
await this.#handleUndoBlock(bot, undoActivity, activity)
|
|
670
|
+
break
|
|
671
|
+
case AS2 + 'Follow':
|
|
672
|
+
await this.#handleUndoFollow(bot, undoActivity, activity)
|
|
673
|
+
break
|
|
674
|
+
default:
|
|
675
|
+
this.#logger.warn({
|
|
676
|
+
msg: 'Unhandled undo',
|
|
677
|
+
undoActivity: undoActivity.id,
|
|
678
|
+
activity: activity.id,
|
|
679
|
+
type: activity.type
|
|
680
|
+
})
|
|
681
|
+
break
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async #handleUndoLike (bot, undoActivity, likeActivity) {
|
|
686
|
+
const actor = this.#getActor(undoActivity)
|
|
687
|
+
likeActivity = await this.#ensureProps(bot, undoActivity, likeActivity, ['object'])
|
|
688
|
+
let object = this.#getObject(likeActivity)
|
|
689
|
+
if (!this.#formatter.isLocal(object.id)) {
|
|
690
|
+
this.#logger.warn({
|
|
691
|
+
msg: 'Undo activity object is not local',
|
|
692
|
+
activity: undoActivity.id,
|
|
693
|
+
likeActivity: likeActivity.id,
|
|
694
|
+
object: object.id
|
|
695
|
+
})
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
object = await this.#objectStorage.read(object.id)
|
|
700
|
+
} catch (err) {
|
|
701
|
+
this.#logger.warn({
|
|
702
|
+
msg: 'Like activity object not found',
|
|
703
|
+
activity: undoActivity.id,
|
|
704
|
+
likeActivity: likeActivity.id,
|
|
705
|
+
object: object.id
|
|
706
|
+
})
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
if (!(await this.#authz.canRead(actor, object))) {
|
|
710
|
+
this.#logger.warn({
|
|
711
|
+
msg: 'Like activity object is not readable',
|
|
712
|
+
activity: undoActivity.id,
|
|
713
|
+
likeActivity: likeActivity.id,
|
|
714
|
+
object: object.id
|
|
715
|
+
})
|
|
716
|
+
return
|
|
717
|
+
}
|
|
718
|
+
this.#logger.info({
|
|
719
|
+
msg: 'Removing like',
|
|
720
|
+
actor: actor.id,
|
|
721
|
+
object: object.id,
|
|
722
|
+
likeActivity: likeActivity.id,
|
|
723
|
+
undoActivity: undoActivity.id
|
|
724
|
+
})
|
|
725
|
+
await this.#objectStorage.removeFromCollection(object.id, 'likes', likeActivity)
|
|
726
|
+
await this.#objectStorage.removeFromCollection(object.id, 'likers', actor)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async #handleUndoAnnounce (bot, undoActivity, shareActivity) {
|
|
730
|
+
const actor = this.#getActor(undoActivity)
|
|
731
|
+
shareActivity = await this.#ensureProps(bot, undoActivity, shareActivity, ['object'])
|
|
732
|
+
let object = this.#getObject(shareActivity)
|
|
733
|
+
if (!this.#formatter.isLocal(object.id)) {
|
|
734
|
+
this.#logger.warn({
|
|
735
|
+
msg: 'Undo activity object is not local',
|
|
736
|
+
activity: undoActivity.id,
|
|
737
|
+
shareActivity: shareActivity.id,
|
|
738
|
+
object: object.id
|
|
739
|
+
})
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
object = await this.#objectStorage.read(object.id)
|
|
744
|
+
} catch (err) {
|
|
745
|
+
this.#logger.warn({
|
|
746
|
+
msg: 'Share activity object not found',
|
|
747
|
+
activity: undoActivity.id,
|
|
748
|
+
shareActivity: shareActivity.id,
|
|
749
|
+
object: object.id
|
|
750
|
+
})
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
if (!(await this.#authz.canRead(actor, object))) {
|
|
754
|
+
this.#logger.warn({
|
|
755
|
+
msg: 'Share activity object is not readable',
|
|
756
|
+
activity: undoActivity.id,
|
|
757
|
+
shareActivity: shareActivity.id,
|
|
758
|
+
object: object.id
|
|
759
|
+
})
|
|
760
|
+
return
|
|
761
|
+
}
|
|
762
|
+
this.#logger.info({
|
|
763
|
+
msg: 'Removing share',
|
|
764
|
+
actor: actor.id,
|
|
765
|
+
object: object.id,
|
|
766
|
+
shareActivity: shareActivity.id,
|
|
767
|
+
undoActivity: undoActivity.id
|
|
768
|
+
})
|
|
769
|
+
await this.#objectStorage.removeFromCollection(object.id, 'shares', shareActivity)
|
|
770
|
+
await this.#objectStorage.removeFromCollection(object.id, 'sharers', actor)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async #handleUndoBlock (bot, undoActivity, blockActivity) {
|
|
774
|
+
const actor = this.#getActor(undoActivity)
|
|
775
|
+
blockActivity = await this.#ensureProps(bot, undoActivity, blockActivity, ['object'])
|
|
776
|
+
const object = this.#getObject(blockActivity)
|
|
777
|
+
if (object.id !== this.#botId(bot)) {
|
|
778
|
+
this.#logger.warn({
|
|
779
|
+
msg: 'Block activity object is not the bot',
|
|
780
|
+
activity: undoActivity.id,
|
|
781
|
+
object: object.id
|
|
782
|
+
})
|
|
783
|
+
} else {
|
|
784
|
+
this.#logger.info({
|
|
785
|
+
msg: 'Block removed',
|
|
786
|
+
actor: actor.id,
|
|
787
|
+
undoActivity: undoActivity.id,
|
|
788
|
+
blockActivity: blockActivity.id,
|
|
789
|
+
object: object.id
|
|
790
|
+
})
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async #handleUndoFollow (bot, undoActivity, followActivity) {
|
|
795
|
+
const actor = this.#getActor(undoActivity)
|
|
796
|
+
followActivity = await this.#ensureProps(bot, undoActivity, followActivity, ['object'])
|
|
797
|
+
const object = this.#getObject(followActivity)
|
|
798
|
+
if (object.id !== this.#botId(bot)) {
|
|
799
|
+
this.#logger.warn({
|
|
800
|
+
msg: 'Follow activity object is not the bot',
|
|
801
|
+
activity: undoActivity.id,
|
|
802
|
+
object: object.id
|
|
803
|
+
})
|
|
804
|
+
return
|
|
805
|
+
}
|
|
806
|
+
if (!(await this.#actorStorage.isInCollection(bot.username, 'followers', actor))) {
|
|
807
|
+
this.#logger.warn({
|
|
808
|
+
msg: 'Undo follow activity from actor not in followers',
|
|
809
|
+
activity: undoActivity.id,
|
|
810
|
+
followActivity: followActivity.id,
|
|
811
|
+
actor: actor.id
|
|
812
|
+
})
|
|
813
|
+
return
|
|
814
|
+
}
|
|
815
|
+
await this.#actorStorage.removeFromCollection(bot.username, 'followers', actor)
|
|
816
|
+
await this.#actorStorage.removeFromCollection(bot.username, 'pendingFollowers', actor)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async onIdle () {
|
|
820
|
+
await this.#distributor.onIdle()
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
#getRecipients (obj) {
|
|
824
|
+
const to = obj.to ? Array.from(obj.to).map((to) => to.id) : null
|
|
825
|
+
const cc = obj.cc ? Array.from(obj.cc).map((cc) => cc.id) : null
|
|
826
|
+
const bto = obj.bto ? Array.from(obj.bto).map((bto) => bto.id) : null
|
|
827
|
+
const bcc = obj.bcc ? Array.from(obj.bcc).map((bcc) => bcc.id) : null
|
|
828
|
+
const audience = obj.audience
|
|
829
|
+
? Array.from(obj.audience).map((audience) => audience.id)
|
|
830
|
+
: null
|
|
831
|
+
return { to, cc, bto, bcc, audience }
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
#removeRecipient (recipients, actor) {
|
|
835
|
+
const remove = (list) => {
|
|
836
|
+
if (!list) {
|
|
837
|
+
return
|
|
838
|
+
}
|
|
839
|
+
const index = list.indexOf(actor.id)
|
|
840
|
+
if (index !== -1) {
|
|
841
|
+
list.splice(index, 1)
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
remove(recipients.to)
|
|
845
|
+
remove(recipients.cc)
|
|
846
|
+
remove(recipients.bto)
|
|
847
|
+
remove(recipients.bcc)
|
|
848
|
+
remove(recipients.audience)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
#addRecipient (recipients, actor, key = 'to') {
|
|
852
|
+
if (!actor.id) {
|
|
853
|
+
return
|
|
854
|
+
}
|
|
855
|
+
if (!recipients[key]) {
|
|
856
|
+
recipients[key] = []
|
|
857
|
+
}
|
|
858
|
+
if (recipients[key].indexOf(actor.id) === -1) {
|
|
859
|
+
recipients[key].push(actor.id)
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
async #doActivity (bot, activity) {
|
|
864
|
+
await this.#objectStorage.create(activity)
|
|
865
|
+
await this.#actorStorage.addToCollection(bot.username, 'outbox', activity)
|
|
866
|
+
await this.#actorStorage.addToCollection(bot.username, 'inbox', activity)
|
|
867
|
+
await this.#distributor.distribute(activity, bot.username)
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
#getActor (activity) {
|
|
871
|
+
return activity.actor?.first
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
#getObject (activity) {
|
|
875
|
+
return activity.object?.first
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#getTarget (activity) {
|
|
879
|
+
return activity.target?.first
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
#getOwner (object) {
|
|
883
|
+
if (object.attributedTo) {
|
|
884
|
+
return object.attributedTo.first
|
|
885
|
+
} else if (object.actor) {
|
|
886
|
+
return object.actor.first
|
|
887
|
+
} else if (object.owner) {
|
|
888
|
+
return object.owner.first
|
|
889
|
+
} else {
|
|
890
|
+
return null
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
async #ensureProps (bot, source, object, required = ['id']) {
|
|
895
|
+
const others = required.filter((prop) => prop !== 'id' && prop !== 'type')
|
|
896
|
+
if (!object.id) {
|
|
897
|
+
return null
|
|
898
|
+
}
|
|
899
|
+
this.#logger.debug({ msg: 'Ensuring object', source: source.id, object: object.id, required })
|
|
900
|
+
// Try getting the object from the source
|
|
901
|
+
if (this.#authz.sameOrigin(source, object) &&
|
|
902
|
+
(!required.includes('type') || object.type) &&
|
|
903
|
+
!others.find((prop) => !object.has(prop))) {
|
|
904
|
+
this.#logger.debug('Object is already complete')
|
|
905
|
+
return object
|
|
906
|
+
}
|
|
907
|
+
// Check if it is a local object
|
|
908
|
+
if (this.#formatter.isLocal(object.id)) {
|
|
909
|
+
this.#logger.debug({ msg: 'Checking local', object: object.id })
|
|
910
|
+
object = await this.#objectStorage.read(object.id)
|
|
911
|
+
if (object &&
|
|
912
|
+
(!required.includes('type') || object.type) &&
|
|
913
|
+
!others.find((prop) => !object.has(prop))) {
|
|
914
|
+
this.#logger.debug('Object fetched from storage')
|
|
915
|
+
return object
|
|
916
|
+
} else {
|
|
917
|
+
return null
|
|
918
|
+
}
|
|
919
|
+
} else {
|
|
920
|
+
// Check it from cache
|
|
921
|
+
const id = object.id
|
|
922
|
+
this.#logger.debug({ msg: 'Checking cache', object: id })
|
|
923
|
+
object = await this.#cache.get(id)
|
|
924
|
+
if (object &&
|
|
925
|
+
(!required.includes('type') || object.type) &&
|
|
926
|
+
!others.find((prop) => !object.has(prop))) {
|
|
927
|
+
this.#logger.debug('Object fetched from cache')
|
|
928
|
+
return object
|
|
929
|
+
}
|
|
930
|
+
// Fetch it from the Web
|
|
931
|
+
this.#logger.debug({ msg: 'Checking remote', object: id })
|
|
932
|
+
object = await this.#client.get(id, bot.username)
|
|
933
|
+
this.#logger.debug({ msg: 'Object fetched from remote', object: object.id, objectText: await object.write() })
|
|
934
|
+
this.#cache.save(object)
|
|
935
|
+
if (object &&
|
|
936
|
+
(!required.includes('type') || object.type) &&
|
|
937
|
+
!others.find((prop) => !object.has(prop))) {
|
|
938
|
+
this.#logger.debug({ msg: 'Object fetched from remote is correct', object: object.id, objectText: await object.write() })
|
|
939
|
+
return object
|
|
940
|
+
}
|
|
941
|
+
return null
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
#botId (bot) {
|
|
946
|
+
return this.#formatter.format({ username: bot.username })
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
async #botActor (bot) {
|
|
950
|
+
return await as2.import({
|
|
951
|
+
id: this.#formatter.format({ username: bot.username })
|
|
952
|
+
})
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
#isMention (bot, object) {
|
|
956
|
+
if (!object.tag) {
|
|
957
|
+
return false
|
|
958
|
+
}
|
|
959
|
+
const url = this.#formatter.format({ username: bot.username })
|
|
960
|
+
for (const tag of object.tag) {
|
|
961
|
+
if (tag.type === AS2 + 'Mention' && tag.href === url) {
|
|
962
|
+
return true
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return false
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
async #objectThread (object) {
|
|
969
|
+
const threadProp = object.get(THREAD_PROP)
|
|
970
|
+
if (threadProp) {
|
|
971
|
+
return Array.from(threadProp)[0]
|
|
972
|
+
}
|
|
973
|
+
const contextProp = object.get('context')
|
|
974
|
+
if (contextProp) {
|
|
975
|
+
return Array.from(contextProp)[0]
|
|
976
|
+
}
|
|
977
|
+
const inReplyTo = object.inReplyTo?.first
|
|
978
|
+
if (
|
|
979
|
+
inReplyTo &&
|
|
980
|
+
this.#formatter.isLocal(inReplyTo.id)
|
|
981
|
+
) {
|
|
982
|
+
const inReplyToObject = await this.#objectStorage.read(inReplyTo.id)
|
|
983
|
+
return await this.#objectThread(inReplyToObject)
|
|
984
|
+
}
|
|
985
|
+
return null
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
async #threadRoot (thread) {
|
|
989
|
+
const parts = this.#formatter.unformat(thread.id)
|
|
990
|
+
if (parts.username && parts.type && parts.nanoid && parts.collection === 'thread') {
|
|
991
|
+
const id = this.#formatter.format(
|
|
992
|
+
{ username: parts.username, type: parts.type, nanoid: parts.nanoid }
|
|
993
|
+
)
|
|
994
|
+
return await this.#objectStorage.read(id)
|
|
995
|
+
} else {
|
|
996
|
+
return null
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|