@evanp/activitypub-bot 0.45.4 → 0.45.6

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 CHANGED
@@ -9,6 +9,21 @@ and this project adheres to
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.45.6] - 2026-04-27
13
+
14
+ ### Fixed
15
+
16
+ - Patch up database problem where actors are
17
+ stored in `pendingFollowing` instead of
18
+ `Follow` activities.
19
+
20
+ ## [0.45.5] - 2026-04-27
21
+
22
+ ### Fixed
23
+
24
+ - Clear stale follow requests in followback bot
25
+ - Synchronize followers collection in followback bot
26
+
12
27
  ## [0.45.4] - 2026-04-26
13
28
 
14
29
  ### Fixed
package/lib/botcontext.js CHANGED
@@ -278,6 +278,15 @@ export class BotContext {
278
278
  'pendingFollowing',
279
279
  followActivity
280
280
  )
281
+ // Buggy code used to put the actor in
282
+ // the collection
283
+ if (await this.#actorStorage.isInCollection(this.#botId, 'pendingFollowing', actor)) {
284
+ await this.#actorStorage.removeFromCollection(
285
+ this.#botId,
286
+ 'pendingFollowing',
287
+ actor
288
+ )
289
+ }
281
290
  }
282
291
  await this.#actorStorage.removeFromCollection(
283
292
  this.#botId,
@@ -487,6 +496,10 @@ export class BotContext {
487
496
  yield * this.#actorStorage.items(this.#botId, 'following')
488
497
  }
489
498
 
499
+ async * pendingFollowing () {
500
+ yield * this.#actorStorage.items(this.#botId, 'pendingFollowing')
501
+ }
502
+
490
503
  async addFollowingUnsafe (actor) {
491
504
  assert.ok(actor)
492
505
  assert.equal(typeof actor, 'object')
@@ -3,14 +3,33 @@ import Bot from '../bot.js'
3
3
  const DEFAULT_NAME = 'FollowBackBot'
4
4
  const DEFAULT_DESCRIPTION = 'A bot that follows you back'
5
5
 
6
+ const NS = 'https://www.w3.org/ns/activitystreams#'
7
+ const FOLLOW = `${NS}Follow`
8
+
9
+ // 7-day default timeout
10
+
11
+ const DEFAULT_STALE_FOLLOW_TIMEOUT = 7 * 24 * 60 * 60 * 1000
12
+
6
13
  export default class FollowBackBot extends Bot {
7
14
  #fullname
8
15
  #description
16
+ #staleFollowTimeout
9
17
 
10
18
  constructor (username, options = {}) {
11
19
  super(username, options)
12
20
  this.#fullname = options.fullname || DEFAULT_NAME
13
21
  this.#description = options.description || DEFAULT_DESCRIPTION
22
+ this.#staleFollowTimeout = ('staleFollowTimeout' in options)
23
+ ? options.staleFollowTimeout
24
+ : DEFAULT_STALE_FOLLOW_TIMEOUT
25
+ }
26
+
27
+ async initialize (context) {
28
+ await super.initialize(context)
29
+ await this.#undoStalePendingFollowing()
30
+ // Drain the queue so undos arrive before re-follows
31
+ await this._context.onIdle()
32
+ await this.#synchronizeFollowers()
14
33
  }
15
34
 
16
35
  get fullname () {
@@ -30,4 +49,67 @@ export default class FollowBackBot extends Bot {
30
49
  this._context.logger.info({ actorId: actor.id }, 'Unfollowing user back')
31
50
  await this._context.unfollowActor(actor)
32
51
  }
52
+
53
+ async #synchronizeFollowers () {
54
+ for await (const follower of this._context.followers()) {
55
+ try {
56
+ if (!await this._context.isFollowing(follower) &&
57
+ !await this._context.isPendingFollowing(follower)) {
58
+ await this._context.followActor(follower)
59
+ this._context.logger.info(
60
+ {
61
+ actorId: follower.id
62
+ },
63
+ 'Synchronized a follower not yet followed'
64
+ )
65
+ }
66
+ } catch (err) {
67
+ this._context.logger.error(
68
+ {
69
+ err,
70
+ follower: follower.id
71
+ },
72
+ 'Error checking for followback; skipping'
73
+ )
74
+ }
75
+ }
76
+ }
77
+
78
+ async #undoStalePendingFollowing () {
79
+ const now = new Date()
80
+ for await (const follow of this._context.pendingFollowing()) {
81
+ try {
82
+ const activity = await this._context.getObject(follow.id)
83
+ if (activity.type === FOLLOW) {
84
+ if (activity.published && (now - activity.published > this.#staleFollowTimeout)) {
85
+ await this._context.unfollowActor(activity.object.first)
86
+ this._context.logger.info(
87
+ {
88
+ actorId: activity.object.first?.id,
89
+ published: activity.published
90
+ },
91
+ 'Unfollowed stale actor'
92
+ )
93
+ }
94
+ } else if (activity.inbox) {
95
+ const actor = activity
96
+ this._context.logger.warn(
97
+ {
98
+ actorId: actor.id
99
+ },
100
+ 'actor incorrectly in pendingFollowing'
101
+ )
102
+ await this._context.unfollowActor(actor)
103
+ }
104
+ } catch (err) {
105
+ this._context.logger.error(
106
+ {
107
+ err,
108
+ activity: follow.id
109
+ },
110
+ 'Error checking stale follow; skipping'
111
+ )
112
+ }
113
+ }
114
+ }
33
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.45.4",
3
+ "version": "0.45.6",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",