@evanp/activitypub-bot 0.46.4 → 0.47.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/CHANGELOG.md +21 -0
- package/README.md +29 -0
- package/lib/activityhandler.js +8 -2
- package/lib/botcontext.js +7 -0
- package/lib/bots/group.js +36 -0
- package/lib/bots/groupfactory.js +27 -0
- package/lib/index.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,27 @@ and this project adheres to
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.47.1] - 2026-05-28
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- GroupBot, GroupBotFactory exported from module
|
|
17
|
+
- Unhandled jobs in BotContext, LitePubRelayClient tests
|
|
18
|
+
|
|
19
|
+
## [0.47.0] - 2026-05-28
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- getLastActivity() method on BotContext
|
|
24
|
+
- GroupBot class
|
|
25
|
+
- GroupBotFactory class
|
|
26
|
+
|
|
27
|
+
## [0.46.5] - 2026-05-25
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Several ownership checks in ActivityHandler
|
|
32
|
+
|
|
12
33
|
## [0.46.4] - 2026-05-25
|
|
13
34
|
|
|
14
35
|
### Added
|
package/README.md
CHANGED
|
@@ -240,6 +240,29 @@ A *LitePubRelayClientBot* can be the client of a LitePub (Pleroma) relay.
|
|
|
240
240
|
|
|
241
241
|
A *LitePubRelayServerBot* will act as a relay server for remote Pleroma servers and other LitePub-relay-compliant servers.
|
|
242
242
|
|
|
243
|
+
#### GroupBot
|
|
244
|
+
|
|
245
|
+
A *GroupBot* is a simple [FEP
|
|
246
|
+
1b12](https://codeberg.org/fediverse/fep/src/branch/main/fep/1b12/fep-1b12.md)-compatible
|
|
247
|
+
group object. When mentioned, it will boost the mentioning object to all its followers.
|
|
248
|
+
|
|
249
|
+
### Pre-installed factory classes
|
|
250
|
+
|
|
251
|
+
A [BotFactory](#botfactory) lets you create a whole class of bots that are
|
|
252
|
+
magically created as needed.
|
|
253
|
+
|
|
254
|
+
#### GroupBotFactory
|
|
255
|
+
|
|
256
|
+
This factory class lets you add a lot of groups at once. Just include this line
|
|
257
|
+
in your bot config:
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
'*': new GroupBotFactory()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Any bot names that don't match one of the other lines in the bot config will be
|
|
264
|
+
passed to the group bot factory.
|
|
265
|
+
|
|
243
266
|
## API
|
|
244
267
|
|
|
245
268
|
Custom bots can implement the [Bot](#bot) interface, which is easiest if you inherit from the `Bot` class.
|
|
@@ -517,6 +540,12 @@ bot can forward a public activity it received through a side-channel
|
|
|
517
540
|
(e.g. a relay-forwarded `Announce`) to all local bots. Errors in any
|
|
518
541
|
individual bot's `onPublic` are logged and do not interrupt the fanout.
|
|
519
542
|
|
|
543
|
+
#### async getLastActivity (type, object)
|
|
544
|
+
|
|
545
|
+
Get the id of the last activity that has the type `type` and the object
|
|
546
|
+
`object`. Great for double-checking if an object has been `Announce`d or
|
|
547
|
+
`Follow`ed before.
|
|
548
|
+
|
|
520
549
|
#### async onIdle ()
|
|
521
550
|
|
|
522
551
|
Resolves when the background distribution queue has drained. Intended for test code that needs to wait for outbound activities to finish being delivered before asserting on their effects.
|
package/lib/activityhandler.js
CHANGED
|
@@ -47,6 +47,12 @@ export class ActivityHandler {
|
|
|
47
47
|
'activity parameter must be an object'
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
const actor = activity.actor?.first
|
|
51
|
+
|
|
52
|
+
if (!actor || !await this.#authz.sameOrigin(activity, actor)) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
await this.#cache.set(
|
|
51
57
|
activity.id,
|
|
52
58
|
bot.username,
|
|
@@ -135,7 +141,7 @@ export class ActivityHandler {
|
|
|
135
141
|
)
|
|
136
142
|
return
|
|
137
143
|
}
|
|
138
|
-
if (this.#authz.isOwner(await this.#botActor(bot), original)) {
|
|
144
|
+
if (await this.#authz.isOwner(await this.#botActor(bot), original)) {
|
|
139
145
|
if (!await this.#authz.canRead(actor, original)) {
|
|
140
146
|
this.#logger.warn(
|
|
141
147
|
'Create activity references inaccessible original object',
|
|
@@ -950,7 +956,7 @@ export class ActivityHandler {
|
|
|
950
956
|
}
|
|
951
957
|
this.#logger.debug({ msg: 'Ensuring object', source: source.id, object: object.id, required })
|
|
952
958
|
// Try getting the object from the source
|
|
953
|
-
if (this.#authz.sameOrigin(source, object) &&
|
|
959
|
+
if (await this.#authz.sameOrigin(source, object) &&
|
|
954
960
|
(!required.includes('type') || object.type) &&
|
|
955
961
|
!others.find((prop) => !object.has(prop))) {
|
|
956
962
|
this.#logger.debug('Object is already complete')
|
package/lib/botcontext.js
CHANGED
|
@@ -549,6 +549,13 @@ export class BotContext {
|
|
|
549
549
|
}))
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
async getLastActivity (type, object) {
|
|
553
|
+
assert.strictEqual(typeof type, 'string')
|
|
554
|
+
assert.strictEqual(typeof object, 'object')
|
|
555
|
+
assert.ok(object !== null)
|
|
556
|
+
return await this.#actorStorage.getLastActivity(this.#botId, type, object)
|
|
557
|
+
}
|
|
558
|
+
|
|
552
559
|
async #isInCollection (name, obj) {
|
|
553
560
|
assert.ok(name)
|
|
554
561
|
assert.equal(typeof name, 'string')
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Bot from '../bot.js'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_DESCRIPTION = 'An echo group'
|
|
4
|
+
|
|
5
|
+
export default class GroupBot extends Bot {
|
|
6
|
+
constructor (username, options = {}) {
|
|
7
|
+
if (typeof username !== 'string') {
|
|
8
|
+
throw new Error('username must be a string')
|
|
9
|
+
}
|
|
10
|
+
if (typeof options !== 'object') {
|
|
11
|
+
throw new Error('options must be an object')
|
|
12
|
+
}
|
|
13
|
+
super(username,
|
|
14
|
+
{
|
|
15
|
+
fullname: `${username} Group`,
|
|
16
|
+
description: DEFAULT_DESCRIPTION,
|
|
17
|
+
...options
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get type () {
|
|
22
|
+
return 'Group'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async onMention (object, activity) {
|
|
26
|
+
const last = await this._context.getLastActivity('Announce', object)
|
|
27
|
+
if (last) {
|
|
28
|
+
this._context.logger.info(
|
|
29
|
+
{ object: object.id, activity: activity.id, last },
|
|
30
|
+
'Skipping re-announce of activity that was previously announced'
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
await this._context.announceObject(object)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
3
|
+
import BotFactory from '../botfactory.js'
|
|
4
|
+
import GroupBot from './group.js'
|
|
5
|
+
|
|
6
|
+
const USERNAME_PATTERN = /^[a-z0-9]{1,64}$/
|
|
7
|
+
|
|
8
|
+
export default class GroupBotFactory extends BotFactory {
|
|
9
|
+
#options
|
|
10
|
+
|
|
11
|
+
constructor (options = {}) {
|
|
12
|
+
super()
|
|
13
|
+
this.#options = options
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async canCreate (username) {
|
|
17
|
+
return USERNAME_PATTERN.test(username)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async create (username) {
|
|
21
|
+
assert.ok(username)
|
|
22
|
+
assert.ok(username.match(USERNAME_PATTERN))
|
|
23
|
+
const bot = new GroupBot(username, this.#options)
|
|
24
|
+
await bot.initialize(await this._context.duplicate(username))
|
|
25
|
+
return bot
|
|
26
|
+
}
|
|
27
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -9,3 +9,5 @@ export { default as FollowBackBot } from './bots/followback.js'
|
|
|
9
9
|
export { default as LitePubRelayClientBot } from './bots/litepubrelayclient.js'
|
|
10
10
|
export { default as LitePubRelayServerBot } from './bots/litepubrelayserver.js'
|
|
11
11
|
export { default as LoggingBot } from './bots/logging.js'
|
|
12
|
+
export { default as GroupBot } from './bots/group.js'
|
|
13
|
+
export { default as GroupBotFactory } from './bots/groupfactory.js'
|