@evanp/activitypub-bot 0.45.4 → 0.45.5

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,13 @@ and this project adheres to
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.45.5] - 2026-04-27
13
+
14
+ ### Fixed
15
+
16
+ - Clear stale follow requests in followback bot
17
+ - Synchronize followers collection in followback bot
18
+
12
19
  ## [0.45.4] - 2026-04-26
13
20
 
14
21
  ### Fixed
package/lib/botcontext.js CHANGED
@@ -487,6 +487,10 @@ export class BotContext {
487
487
  yield * this.#actorStorage.items(this.#botId, 'following')
488
488
  }
489
489
 
490
+ async * pendingFollowing () {
491
+ yield * this.#actorStorage.items(this.#botId, 'pendingFollowing')
492
+ }
493
+
490
494
  async addFollowingUnsafe (actor) {
491
495
  assert.ok(actor)
492
496
  assert.equal(typeof actor, 'object')
@@ -3,14 +3,30 @@ 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
+ // 7-day default timeout
7
+
8
+ const DEFAULT_STALE_FOLLOW_TIMEOUT = 7 * 24 * 60 * 60 * 1000
9
+
6
10
  export default class FollowBackBot extends Bot {
7
11
  #fullname
8
12
  #description
13
+ #staleFollowTimeout
9
14
 
10
15
  constructor (username, options = {}) {
11
16
  super(username, options)
12
17
  this.#fullname = options.fullname || DEFAULT_NAME
13
18
  this.#description = options.description || DEFAULT_DESCRIPTION
19
+ this.#staleFollowTimeout = ('staleFollowTimeout' in options)
20
+ ? options.staleFollowTimeout
21
+ : DEFAULT_STALE_FOLLOW_TIMEOUT
22
+ }
23
+
24
+ async initialize (context) {
25
+ await super.initialize(context)
26
+ await this.#undoStalePendingFollowing()
27
+ // Drain the queue so undos arrive before re-follows
28
+ await this._context.onIdle()
29
+ await this.#synchronizeFollowers()
14
30
  }
15
31
 
16
32
  get fullname () {
@@ -30,4 +46,56 @@ export default class FollowBackBot extends Bot {
30
46
  this._context.logger.info({ actorId: actor.id }, 'Unfollowing user back')
31
47
  await this._context.unfollowActor(actor)
32
48
  }
49
+
50
+ async #synchronizeFollowers () {
51
+ for await (const follower of this._context.followers()) {
52
+ try {
53
+ if (!await this._context.isFollowing(follower) &&
54
+ !await this._context.isPendingFollowing(follower)) {
55
+ await this._context.followActor(follower)
56
+ this._context.logger.info(
57
+ {
58
+ actorId: follower.id
59
+ },
60
+ 'Synchronized a follower not yet followed'
61
+ )
62
+ }
63
+ } catch (err) {
64
+ this._context.logger.error(
65
+ {
66
+ err,
67
+ follower: follower.id
68
+ },
69
+ 'Error checking for followback; skipping'
70
+ )
71
+ }
72
+ }
73
+ }
74
+
75
+ async #undoStalePendingFollowing () {
76
+ const now = new Date()
77
+ for await (const follow of this._context.pendingFollowing()) {
78
+ try {
79
+ const activity = await this._context.getObject(follow.id)
80
+ if (activity.published && (now - activity.published > this.#staleFollowTimeout)) {
81
+ await this._context.unfollowActor(activity.object.first)
82
+ this._context.logger.info(
83
+ {
84
+ actorId: activity.object.first?.id,
85
+ published: activity.published
86
+ },
87
+ 'Unfollowed stale actor'
88
+ )
89
+ }
90
+ } catch (err) {
91
+ this._context.logger.error(
92
+ {
93
+ err,
94
+ activity: follow.id
95
+ },
96
+ 'Error checking stale follow; skipping'
97
+ )
98
+ }
99
+ }
100
+ }
33
101
  }
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.5",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",