@evanp/activitypub-bot 0.24.2 → 0.25.1
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/README.md +17 -0
- package/lib/activityhandler.js +18 -3
- package/lib/bot.js +4 -0
- package/lib/botcontext.js +41 -19
- package/lib/botfactory.js +4 -0
- package/lib/bots/followback.js +33 -0
- package/lib/bots/relayclient.js +12 -7
- package/lib/index.js +1 -0
- package/lib/routes/sharedinbox.js +4 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -157,6 +157,19 @@ An *OKBot* instance will reply to any message that it's mentioned in with the co
|
|
|
157
157
|
|
|
158
158
|
A *DoNothingBot* instance will only do default stuff, like accepting follows.
|
|
159
159
|
|
|
160
|
+
#### FollowBackBot
|
|
161
|
+
|
|
162
|
+
A *FollowBackBot* will follow back anyone who follows it. Useful for collecting
|
|
163
|
+
public information.
|
|
164
|
+
|
|
165
|
+
#### RelayClientBot
|
|
166
|
+
|
|
167
|
+
A *RelayClientBot* can be the client of a Mastodon or Pleroma relay.
|
|
168
|
+
|
|
169
|
+
#### RelayServerBot
|
|
170
|
+
|
|
171
|
+
A *RelayServerBot* will act as a relay server for remote servers.
|
|
172
|
+
|
|
160
173
|
## API
|
|
161
174
|
|
|
162
175
|
Custom bots can implement the [Bot](#bot) interface, which is easiest if you inherit from the `Bot` class.
|
|
@@ -203,6 +216,10 @@ Called when the bot is mentioned in an incoming object. Can be used to implement
|
|
|
203
216
|
Called when the bot is followed by another actor. The first argument is the actor, and the second is the `Follow` activity itself. This method is called after the acceptance has been sent and the new
|
|
204
217
|
follower has been added to the `followers` collection.
|
|
205
218
|
|
|
219
|
+
#### async onUndoFollow (actor, undoActivity, followActivity)
|
|
220
|
+
|
|
221
|
+
Called when the bot is unfollowed by another actor. The first argument is the actor, the second is the `Undo` activity, and the third is the `Follow` activity. This method is called after the `followers` and/or `pendingFollowers` collections have been updated.
|
|
222
|
+
|
|
206
223
|
#### async onLike (object, activity)
|
|
207
224
|
|
|
208
225
|
Called when one of the bot's objects is liked by another actor. The first argument is the object, and the second is the `Like` activity itself. This method is called after the like has been added to the
|
package/lib/activityhandler.js
CHANGED
|
@@ -804,13 +804,16 @@ export class ActivityHandler {
|
|
|
804
804
|
this.#logger.warn({
|
|
805
805
|
msg: 'Follow activity object is not the bot',
|
|
806
806
|
activity: undoActivity.id,
|
|
807
|
-
object: object.id
|
|
807
|
+
object: object.id,
|
|
808
|
+
botId: this.#botId(bot)
|
|
808
809
|
})
|
|
809
810
|
return
|
|
810
811
|
}
|
|
811
|
-
if (
|
|
812
|
+
if (
|
|
813
|
+
!(await this.#actorStorage.isInCollection(bot.username, 'followers', actor)) &&
|
|
814
|
+
!(await this.#actorStorage.isInCollection(bot.username, 'pendingFollowers', actor))) {
|
|
812
815
|
this.#logger.warn({
|
|
813
|
-
msg: 'Undo follow activity from actor not in followers',
|
|
816
|
+
msg: 'Undo follow activity from actor not in followers or pendingFollowers',
|
|
814
817
|
activity: undoActivity.id,
|
|
815
818
|
followActivity: followActivity.id,
|
|
816
819
|
actor: actor.id
|
|
@@ -819,6 +822,18 @@ export class ActivityHandler {
|
|
|
819
822
|
}
|
|
820
823
|
await this.#actorStorage.removeFromCollection(bot.username, 'followers', actor)
|
|
821
824
|
await this.#actorStorage.removeFromCollection(bot.username, 'pendingFollowers', actor)
|
|
825
|
+
this.#logger.debug(
|
|
826
|
+
{ bot: this.#botId(bot), activity: undoActivity.id },
|
|
827
|
+
'Notifying bot of undo follow activity'
|
|
828
|
+
)
|
|
829
|
+
try {
|
|
830
|
+
await bot.onUndoFollow(actor, undoActivity, followActivity)
|
|
831
|
+
} catch (err) {
|
|
832
|
+
this.#logger.warn(
|
|
833
|
+
{ err, bot: this.#botId(bot), activity: undoActivity.id },
|
|
834
|
+
'Error notifying bot of undo follow activity'
|
|
835
|
+
)
|
|
836
|
+
}
|
|
822
837
|
}
|
|
823
838
|
|
|
824
839
|
async onIdle () {
|
package/lib/bot.js
CHANGED
package/lib/botcontext.js
CHANGED
|
@@ -224,17 +224,32 @@ export class BotContext {
|
|
|
224
224
|
async followActor (actor) {
|
|
225
225
|
assert.ok(actor)
|
|
226
226
|
assert.equal(typeof actor, 'object')
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
227
|
+
let activity
|
|
228
|
+
if (await this.#actorStorage.isInCollection(this.#botId, 'following', actor) ||
|
|
229
|
+
await this.#actorStorage.isInCollection(this.#botId, 'pendingFollowing', actor)) {
|
|
230
|
+
const originalId = await this.#actorStorage.getLastActivity(
|
|
231
|
+
this.#botId,
|
|
232
|
+
'Follow',
|
|
233
|
+
actor
|
|
234
|
+
)
|
|
235
|
+
if (!originalId) {
|
|
236
|
+
throw new Error(`no Follow activity for ${actor.id}`)
|
|
237
|
+
}
|
|
238
|
+
activity = await this.#objectStorage.read(originalId)
|
|
239
|
+
} else {
|
|
240
|
+
await this.#actorStorage.addToCollection(
|
|
241
|
+
this.#botId,
|
|
242
|
+
'pendingFollowing',
|
|
243
|
+
actor
|
|
244
|
+
)
|
|
245
|
+
activity = await this.#doActivity({
|
|
246
|
+
type: 'Follow',
|
|
247
|
+
object: actor.id,
|
|
248
|
+
to: actor.id
|
|
249
|
+
})
|
|
250
|
+
await this.#actorStorage.setLastActivity(this.#botId, activity)
|
|
251
|
+
}
|
|
252
|
+
|
|
238
253
|
return activity
|
|
239
254
|
}
|
|
240
255
|
|
|
@@ -420,13 +435,13 @@ export class BotContext {
|
|
|
420
435
|
#getRecipients (obj) {
|
|
421
436
|
assert.ok(obj)
|
|
422
437
|
assert.strictEqual(typeof obj, 'object', 'obj must be an object')
|
|
423
|
-
const to = obj.to ? Array.from(obj.to).map((to) => to.id) :
|
|
424
|
-
const cc = obj.cc ? Array.from(obj.cc).map((cc) => cc.id) :
|
|
425
|
-
const bto = obj.bto ? Array.from(obj.bto).map((bto) => bto.id) :
|
|
426
|
-
const bcc = obj.bcc ? Array.from(obj.bcc).map((bcc) => bcc.id) :
|
|
438
|
+
const to = obj.to ? Array.from(obj.to).map((to) => to.id) : undefined
|
|
439
|
+
const cc = obj.cc ? Array.from(obj.cc).map((cc) => cc.id) : undefined
|
|
440
|
+
const bto = obj.bto ? Array.from(obj.bto).map((bto) => bto.id) : undefined
|
|
441
|
+
const bcc = obj.bcc ? Array.from(obj.bcc).map((bcc) => bcc.id) : undefined
|
|
427
442
|
const audience = obj.audience
|
|
428
443
|
? Array.from(obj.audience).map((audience) => audience.id)
|
|
429
|
-
:
|
|
444
|
+
: undefined
|
|
430
445
|
return { to, cc, bto, bcc, audience }
|
|
431
446
|
}
|
|
432
447
|
|
|
@@ -445,7 +460,7 @@ export class BotContext {
|
|
|
445
460
|
async #doActivity (activityData, distribute = true) {
|
|
446
461
|
const now = new Date().toISOString()
|
|
447
462
|
const type = activityData.type || 'Activity'
|
|
448
|
-
const
|
|
463
|
+
const json = {
|
|
449
464
|
...activityData,
|
|
450
465
|
type,
|
|
451
466
|
id: this.#formatter.format({
|
|
@@ -459,7 +474,14 @@ export class BotContext {
|
|
|
459
474
|
},
|
|
460
475
|
published: now,
|
|
461
476
|
updated: now
|
|
462
|
-
}
|
|
477
|
+
}
|
|
478
|
+
let activity
|
|
479
|
+
try {
|
|
480
|
+
activity = await as2.import(json)
|
|
481
|
+
} catch (err) {
|
|
482
|
+
this.#logger.warn({ err, json, method: '#doActivity' }, 'Import error')
|
|
483
|
+
throw err
|
|
484
|
+
}
|
|
463
485
|
await this.#objectStorage.create(activity)
|
|
464
486
|
await this.#actorStorage.addToCollection(this.#botId, 'outbox', activity)
|
|
465
487
|
await this.#actorStorage.addToCollection(this.#botId, 'inbox', activity)
|
|
@@ -488,7 +510,7 @@ export class BotContext {
|
|
|
488
510
|
type,
|
|
489
511
|
object: {
|
|
490
512
|
id: obj.id,
|
|
491
|
-
type: obj.type
|
|
513
|
+
...(obj.type && { type: obj.type })
|
|
492
514
|
}
|
|
493
515
|
},
|
|
494
516
|
...recipients
|
package/lib/botfactory.js
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Bot from '../bot.js'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_NAME = 'FollowBackBot'
|
|
4
|
+
const DEFAULT_DESCRIPTION = 'A bot that follows you back'
|
|
5
|
+
|
|
6
|
+
export default class FollowBackBot extends Bot {
|
|
7
|
+
#fullname
|
|
8
|
+
#description
|
|
9
|
+
|
|
10
|
+
constructor (username, { fullname = DEFAULT_NAME, description = DEFAULT_DESCRIPTION } = {}) {
|
|
11
|
+
super(username)
|
|
12
|
+
this.#fullname = fullname
|
|
13
|
+
this.#description = description
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get fullname () {
|
|
17
|
+
return this.#fullname
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get description () {
|
|
21
|
+
return this.#description
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async onFollow (actor, activity) {
|
|
25
|
+
this._context.logger.info({ actorId: actor.id }, 'Following user back')
|
|
26
|
+
await this._context.followActor(actor)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async onUndoFollow (actor, undoActivity, followActivity) {
|
|
30
|
+
this._context.logger.info({ actorId: actor.id }, 'Unfollowing user back')
|
|
31
|
+
await this._context.unfollowActor(actor)
|
|
32
|
+
}
|
|
33
|
+
}
|
package/lib/bots/relayclient.js
CHANGED
|
@@ -22,6 +22,10 @@ export default class RelayClientBot extends Bot {
|
|
|
22
22
|
return 'A bot for subscribing to relays'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
get key () {
|
|
26
|
+
return `follow:${this.#relay}`
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
async initialize (context) {
|
|
26
30
|
super.initialize(context)
|
|
27
31
|
this._context.logger.info(
|
|
@@ -29,11 +33,12 @@ export default class RelayClientBot extends Bot {
|
|
|
29
33
|
'Initialising relay client'
|
|
30
34
|
)
|
|
31
35
|
if (this.#unsubscribe) {
|
|
32
|
-
if (await this._context.hasData(
|
|
36
|
+
if (await this._context.hasData(this.key)) {
|
|
33
37
|
await this.#unfollowRelay()
|
|
34
38
|
}
|
|
35
39
|
} else {
|
|
36
|
-
if (!(await this._context.hasData(
|
|
40
|
+
if (!(await this._context.hasData(this.key)) ||
|
|
41
|
+
((await this._context.getData(this.key)) == null)) {
|
|
37
42
|
await this.#followRelay()
|
|
38
43
|
}
|
|
39
44
|
}
|
|
@@ -70,11 +75,11 @@ export default class RelayClientBot extends Bot {
|
|
|
70
75
|
{ relay: this.#relay, activity: activity.id },
|
|
71
76
|
'Saving follow for later'
|
|
72
77
|
)
|
|
73
|
-
this._context.setData(
|
|
78
|
+
this._context.setData(this.key, activity.id)
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
async #unfollowRelay () {
|
|
77
|
-
const activityId = await this._context.getData(
|
|
82
|
+
const activityId = await this._context.getData(this.key)
|
|
78
83
|
this._context.logger.info(
|
|
79
84
|
{ relay: this.#relay, activity: activityId },
|
|
80
85
|
'Unfollowing relay'
|
|
@@ -92,11 +97,11 @@ export default class RelayClientBot extends Bot {
|
|
|
92
97
|
{ relay: this.#relay },
|
|
93
98
|
'Clearing follow data'
|
|
94
99
|
)
|
|
95
|
-
this._context.
|
|
100
|
+
this._context.deleteData(this.key)
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
async #handleAccept (activity) {
|
|
99
|
-
const activityId = await this._context.getData(
|
|
104
|
+
const activityId = await this._context.getData(this.key)
|
|
100
105
|
if (activity.object?.first?.id === activityId) {
|
|
101
106
|
this._context.logger.info(
|
|
102
107
|
{ accept: activity.id, follow: activityId },
|
|
@@ -109,7 +114,7 @@ export default class RelayClientBot extends Bot {
|
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
async #handleReject (activity) {
|
|
112
|
-
const activityId = await this._context.getData(
|
|
117
|
+
const activityId = await this._context.getData(this.key)
|
|
113
118
|
if (activity.object?.first?.id === activityId) {
|
|
114
119
|
this._context.logger.info(
|
|
115
120
|
{ accept: activity.id, follow: activityId },
|
package/lib/index.js
CHANGED
|
@@ -5,3 +5,4 @@ export { default as OKBot } from './bots/ok.js'
|
|
|
5
5
|
export { default as DoNothingBot } from './bots/donothing.js'
|
|
6
6
|
export { default as RelayClientBot } from './bots/relayclient.js'
|
|
7
7
|
export { default as RelayServerBot } from './bots/relayserver.js'
|
|
8
|
+
export { default as FollowBackBot } from './bots/followback.js'
|
|
@@ -15,7 +15,10 @@ async function asyncSome (array, asyncPredicate) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async function actorOK (subject, activity, bots) {
|
|
18
|
-
await asyncSome(
|
|
18
|
+
return await asyncSome(
|
|
19
|
+
Object.values(bots),
|
|
20
|
+
bot => bot.actorOK(subject, activity)
|
|
21
|
+
)
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
router.post('/shared/inbox', async (req, res, next) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evanp/activitypub-bot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "server-side ActivityPub bot framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"sequelize": "^6.37.7"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@evanp/activitypub-nock": "^0.
|
|
46
|
+
"@evanp/activitypub-nock": "^0.6.0",
|
|
47
47
|
"eslint": "^8.57.1",
|
|
48
48
|
"eslint-config-standard": "^17.1.0",
|
|
49
49
|
"eslint-plugin-import": "^2.29.1",
|