@evanp/activitypub-bot 0.9.0 → 0.11.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/workflows/main.yml +34 -0
- package/.github/workflows/{tag-docker.yml → tag.yml} +57 -5
- package/.nvmrc +1 -0
- package/Dockerfile +11 -16
- package/README.md +87 -13
- package/activitypub-bot.js +68 -0
- package/lib/activitydeliverer.js +260 -0
- package/lib/activityhandler.js +14 -0
- package/lib/activitypubclient.js +52 -1
- package/lib/activitystreams.js +31 -0
- package/lib/actorstorage.js +18 -28
- package/lib/app.js +18 -7
- package/lib/bot.js +7 -0
- package/lib/botcontext.js +62 -0
- package/lib/botdatastorage.js +0 -13
- package/lib/botfactory.js +24 -0
- package/lib/botmaker.js +23 -0
- package/lib/keystorage.js +7 -24
- package/lib/migrations/001-initial.js +107 -0
- package/lib/migrations/index.js +28 -0
- package/lib/objectcache.js +4 -1
- package/lib/objectstorage.js +0 -36
- package/lib/remotekeystorage.js +0 -24
- package/lib/routes/collection.js +6 -2
- package/lib/routes/inbox.js +7 -20
- package/lib/routes/sharedinbox.js +54 -0
- package/lib/routes/user.js +11 -5
- package/lib/routes/webfinger.js +11 -2
- package/lib/urlformatter.js +8 -0
- package/package.json +14 -7
- package/tests/activitydistributor.test.js +3 -3
- package/tests/activityhandler.test.js +96 -5
- package/tests/activitypubclient.test.js +115 -130
- package/tests/actorstorage.test.js +26 -4
- package/tests/authorizer.test.js +3 -8
- package/tests/botcontext.test.js +109 -63
- package/tests/botdatastorage.test.js +3 -2
- package/tests/botfactory.provincebotfactory.test.js +430 -0
- package/tests/fixtures/bots.js +13 -1
- package/tests/fixtures/eventloggingbot.js +57 -0
- package/tests/fixtures/provincebotfactory.js +53 -0
- package/tests/httpsignature.test.js +3 -4
- package/tests/httpsignatureauthenticator.test.js +3 -3
- package/tests/keystorage.test.js +37 -2
- package/tests/microsyntax.test.js +3 -2
- package/tests/objectstorage.test.js +4 -3
- package/tests/remotekeystorage.test.js +10 -8
- package/tests/routes.actor.test.js +7 -0
- package/tests/routes.collection.test.js +0 -1
- package/tests/routes.inbox.test.js +1 -0
- package/tests/routes.object.test.js +44 -38
- package/tests/routes.sharedinbox.test.js +473 -0
- package/tests/routes.webfinger.test.js +27 -0
- package/tests/utils/nock.js +250 -27
- package/.github/workflows/main-docker.yml +0 -45
- package/index.js +0 -23
package/lib/remotekeystorage.js
CHANGED
|
@@ -10,30 +10,6 @@ export class RemoteKeyStorage {
|
|
|
10
10
|
this.#logger = logger.child({ class: this.constructor.name })
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
async initialize () {
|
|
14
|
-
await this.#connection.query(
|
|
15
|
-
`CREATE TABLE IF NOT EXISTS new_remotekeys (
|
|
16
|
-
id VARCHAR(512) PRIMARY KEY,
|
|
17
|
-
owner VARCHAR(512) NOT NULL,
|
|
18
|
-
publicKeyPem TEXT NOT NULL,
|
|
19
|
-
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
20
|
-
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
21
|
-
)`
|
|
22
|
-
)
|
|
23
|
-
try {
|
|
24
|
-
await this.#connection.query(
|
|
25
|
-
`INSERT OR IGNORE INTO new_remotekeys (id, owner, publicKeyPem)
|
|
26
|
-
SELECT id, owner, publicKeyPem
|
|
27
|
-
FROM remotekeys`
|
|
28
|
-
)
|
|
29
|
-
} catch (error) {
|
|
30
|
-
this.#logger.debug(
|
|
31
|
-
{ error, method: 'RemoteKeyStorage.initialize' },
|
|
32
|
-
'failed to copy remotekeys to new_remotekeys table'
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
13
|
async getPublicKey (id, useCache = true) {
|
|
38
14
|
this.debug(`getPublicKey(${id})`)
|
|
39
15
|
if (useCache) {
|
package/lib/routes/collection.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import as2 from '../activitystreams.js'
|
|
3
3
|
import createHttpError from 'http-errors'
|
|
4
|
+
import BotMaker from '../botmaker.js'
|
|
4
5
|
|
|
5
6
|
const router = express.Router()
|
|
6
7
|
|
|
@@ -18,7 +19,8 @@ async function filterAsync (array, asyncPredicate) {
|
|
|
18
19
|
router.get('/user/:username/:collection', async (req, res, next) => {
|
|
19
20
|
const { username, collection } = req.params
|
|
20
21
|
const { actorStorage, bots } = req.app.locals
|
|
21
|
-
|
|
22
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
23
|
+
if (!bot) {
|
|
22
24
|
return next(createHttpError(404, `User ${username} not found`))
|
|
23
25
|
}
|
|
24
26
|
if (collection === 'inbox') {
|
|
@@ -37,10 +39,12 @@ router.get('/user/:username/:collection', async (req, res, next) => {
|
|
|
37
39
|
router.get('/user/:username/:collection/:n(\\d+)', async (req, res, next) => {
|
|
38
40
|
const { username, collection, n } = req.params
|
|
39
41
|
const { actorStorage, bots, authorizer, objectStorage, formatter, client } = req.app.locals
|
|
42
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
40
43
|
|
|
41
|
-
if (!
|
|
44
|
+
if (!bot) {
|
|
42
45
|
return next(createHttpError(404, `User ${username} not found`))
|
|
43
46
|
}
|
|
47
|
+
|
|
44
48
|
if (collection === 'inbox') {
|
|
45
49
|
return next(createHttpError(403, `No access to ${collection} collection`))
|
|
46
50
|
}
|
package/lib/routes/inbox.js
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import as2 from '../activitystreams.js'
|
|
3
3
|
import createHttpError from 'http-errors'
|
|
4
|
+
import BotMaker from '../botmaker.js'
|
|
4
5
|
|
|
5
6
|
const router = express.Router()
|
|
6
7
|
|
|
7
|
-
function isActivity (object) {
|
|
8
|
-
return true
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function getActor (activity) {
|
|
12
|
-
return activity.actor?.first
|
|
13
|
-
}
|
|
14
|
-
|
|
15
8
|
router.post('/user/:username/inbox', async (req, res, next) => {
|
|
16
9
|
const { username } = req.params
|
|
17
|
-
const { bots,
|
|
10
|
+
const { bots, deliverer, actorStorage } = req.app.locals
|
|
18
11
|
const { subject } = req.auth
|
|
19
12
|
const { logger } = req.app.locals
|
|
20
13
|
|
|
21
|
-
const bot = bots
|
|
14
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
22
15
|
if (!bot) {
|
|
23
|
-
return next(createHttpError(404,
|
|
16
|
+
return next(createHttpError(404, `User ${username} not found`))
|
|
24
17
|
}
|
|
25
18
|
|
|
26
19
|
if (!subject) {
|
|
@@ -41,11 +34,11 @@ router.post('/user/:username/inbox', async (req, res, next) => {
|
|
|
41
34
|
return next(createHttpError(400, 'Invalid request body'))
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
if (!isActivity(activity)) {
|
|
37
|
+
if (!deliverer.isActivity(activity)) {
|
|
45
38
|
return next(createHttpError(400, 'Request body is not an activity'))
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
const actor = getActor(activity)
|
|
41
|
+
const actor = deliverer.getActor(activity)
|
|
49
42
|
|
|
50
43
|
if (!actor) {
|
|
51
44
|
return next(createHttpError(400, 'No actor found in activity'))
|
|
@@ -67,13 +60,7 @@ router.post('/user/:username/inbox', async (req, res, next) => {
|
|
|
67
60
|
return next(createHttpError(400, 'Activity already delivered'))
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
await activityHandler.handleActivity(bot, activity)
|
|
72
|
-
} catch (err) {
|
|
73
|
-
return next(err)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
await actorStorage.addToCollection(bot.username, 'inbox', activity)
|
|
63
|
+
await deliverer.deliverTo(activity, bot)
|
|
77
64
|
|
|
78
65
|
res.status(200)
|
|
79
66
|
res.type('text/plain')
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import as2 from '../activitystreams.js'
|
|
3
|
+
import createHttpError from 'http-errors'
|
|
4
|
+
|
|
5
|
+
const router = express.Router()
|
|
6
|
+
|
|
7
|
+
router.post('/shared/inbox', async (req, res, next) => {
|
|
8
|
+
const { bots, deliverer, logger } = req.app.locals
|
|
9
|
+
const { subject } = req.auth
|
|
10
|
+
|
|
11
|
+
if (!subject) {
|
|
12
|
+
return next(createHttpError(401, 'Unauthorized'))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!req.body) {
|
|
16
|
+
return next(createHttpError(400, 'No request body provided'))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let activity
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
activity = await as2.import(req.body)
|
|
23
|
+
} catch (err) {
|
|
24
|
+
logger.warn('Failed to import activity', err)
|
|
25
|
+
logger.debug('Request body', req.body)
|
|
26
|
+
return next(createHttpError(400, 'Invalid request body'))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!deliverer.isActivity(activity)) {
|
|
30
|
+
return next(createHttpError(400, 'Request body is not an activity'))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const actor = deliverer.getActor(activity)
|
|
34
|
+
|
|
35
|
+
if (!actor) {
|
|
36
|
+
return next(createHttpError(400, 'No actor found in activity'))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!actor.id) {
|
|
40
|
+
return next(createHttpError(400, 'No actor id found in activity'))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (actor.id !== subject) {
|
|
44
|
+
return next(createHttpError(403, `${subject} is not the actor ${actor.id}`))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await deliverer.deliverToAll(activity, bots)
|
|
48
|
+
|
|
49
|
+
res.status(200)
|
|
50
|
+
res.type('text/plain')
|
|
51
|
+
res.send('OK')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export default router
|
package/lib/routes/user.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import as2 from '../activitystreams.js'
|
|
3
3
|
import createHttpError from 'http-errors'
|
|
4
|
+
import BotMaker from '../botmaker.js'
|
|
4
5
|
|
|
5
6
|
const router = express.Router()
|
|
6
7
|
|
|
7
8
|
router.get('/user/:username', async (req, res, next) => {
|
|
8
9
|
const { username } = req.params
|
|
9
|
-
const { actorStorage, keyStorage, formatter, bots } = req.app.locals
|
|
10
|
-
|
|
10
|
+
const { actorStorage, keyStorage, formatter, bots, origin } = req.app.locals
|
|
11
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
12
|
+
if (!bot) {
|
|
11
13
|
return next(createHttpError(404, `User ${username} not found`))
|
|
12
14
|
}
|
|
13
15
|
const publicKeyPem = await keyStorage.getPublicKey(username)
|
|
@@ -16,14 +18,17 @@ router.get('/user/:username', async (req, res, next) => {
|
|
|
16
18
|
'https://www.w3.org/ns/activitystreams',
|
|
17
19
|
'https://w3id.org/security/v1'
|
|
18
20
|
],
|
|
19
|
-
name:
|
|
20
|
-
summary:
|
|
21
|
+
name: bot.fullname,
|
|
22
|
+
summary: bot.description,
|
|
21
23
|
publicKey: {
|
|
22
24
|
publicKeyPem,
|
|
23
25
|
id: formatter.format({ username, type: 'publickey' }),
|
|
24
26
|
owner: formatter.format({ username }),
|
|
25
27
|
type: 'CryptographicKey',
|
|
26
28
|
to: 'as:Public'
|
|
29
|
+
},
|
|
30
|
+
endpoints: {
|
|
31
|
+
sharedInbox: `${origin}/shared/inbox`
|
|
27
32
|
}
|
|
28
33
|
})
|
|
29
34
|
res.status(200)
|
|
@@ -37,7 +42,8 @@ router.get('/user/:username', async (req, res, next) => {
|
|
|
37
42
|
router.get('/user/:username/publickey', async (req, res, next) => {
|
|
38
43
|
const { username } = req.params
|
|
39
44
|
const { formatter, keyStorage, bots } = req.app.locals
|
|
40
|
-
|
|
45
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
46
|
+
if (!bot) {
|
|
41
47
|
return next(createHttpError(404, `User ${username} not found`))
|
|
42
48
|
}
|
|
43
49
|
const publicKeyPem = await keyStorage.getPublicKey(username)
|
package/lib/routes/webfinger.js
CHANGED
|
@@ -3,11 +3,14 @@ import createHttpError from 'http-errors'
|
|
|
3
3
|
|
|
4
4
|
const router = Router()
|
|
5
5
|
|
|
6
|
-
router.get('/.well-known/webfinger', (req, res, next) => {
|
|
6
|
+
router.get('/.well-known/webfinger', async (req, res, next) => {
|
|
7
7
|
const { resource } = req.query
|
|
8
8
|
if (!resource) {
|
|
9
9
|
return next(createHttpError(400, 'resource parameter is required'))
|
|
10
10
|
}
|
|
11
|
+
if (resource.substring(0, 5) !== 'acct:') {
|
|
12
|
+
return next(createHttpError(400, 'only acct: protocol supported'))
|
|
13
|
+
}
|
|
11
14
|
const [username, domain] = resource.substring(5).split('@')
|
|
12
15
|
if (!username || !domain) {
|
|
13
16
|
return next(createHttpError(400, 'Invalid resource parameter'))
|
|
@@ -17,7 +20,13 @@ router.get('/.well-known/webfinger', (req, res, next) => {
|
|
|
17
20
|
return next(createHttpError(400, 'Invalid domain in resource parameter'))
|
|
18
21
|
}
|
|
19
22
|
if (!(username in req.app.locals.bots)) {
|
|
20
|
-
|
|
23
|
+
if ('*' in req.app.locals.bots) {
|
|
24
|
+
if (!await req.app.locals.bots['*'].canCreate(username)) {
|
|
25
|
+
return next(createHttpError(404, 'Bot not found'))
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
return next(createHttpError(404, 'Bot not found'))
|
|
29
|
+
}
|
|
21
30
|
}
|
|
22
31
|
res.status(200)
|
|
23
32
|
res.type('application/jrd+json')
|
package/lib/urlformatter.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evanp/activitypub-bot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "server-side ActivityPub bot framework",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "activitypub-bot.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"activitypub-bot": "./activitypub-bot.js"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
11
|
"test": "NODE_ENV=test node --test",
|
|
9
|
-
"start": "
|
|
12
|
+
"start": "npx activitypub-bot"
|
|
10
13
|
},
|
|
11
14
|
"repository": {
|
|
12
15
|
"type": "git",
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
},
|
|
26
29
|
"homepage": "https://github.com/evanp/activitypub-bot#readme",
|
|
27
30
|
"dependencies": {
|
|
28
|
-
"@isaacs/ttlcache": "^1.4
|
|
31
|
+
"@isaacs/ttlcache": "^2.1.4",
|
|
29
32
|
"activitystrea.ms": "3.2",
|
|
30
33
|
"express": "^4.22.1",
|
|
31
34
|
"http-errors": "^2.0.0",
|
|
@@ -33,8 +36,8 @@
|
|
|
33
36
|
"lru-cache": "^11.1.0",
|
|
34
37
|
"nanoid": "^5.1.5",
|
|
35
38
|
"node-fetch": "^3.3.2",
|
|
36
|
-
"p-queue": "^
|
|
37
|
-
"pino": "^
|
|
39
|
+
"p-queue": "^9.1.0",
|
|
40
|
+
"pino": "^10.1.1",
|
|
38
41
|
"pino-http": "^11.0.0",
|
|
39
42
|
"sequelize": "^6.37.7"
|
|
40
43
|
},
|
|
@@ -47,5 +50,9 @@
|
|
|
47
50
|
"mysql2": "^3.9.1",
|
|
48
51
|
"pg": "^8.16.3",
|
|
49
52
|
"sqlite3": "^5.1.7"
|
|
50
|
-
}
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": "^20 || ^22 || ^24 || ^25"
|
|
56
|
+
},
|
|
57
|
+
"engineStrict": true
|
|
51
58
|
}
|
|
@@ -11,6 +11,7 @@ import { ActivityDistributor } from '../lib/activitydistributor.js'
|
|
|
11
11
|
import Logger from 'pino'
|
|
12
12
|
import { HTTPSignature } from '../lib/httpsignature.js'
|
|
13
13
|
import { Digester } from '../lib/digester.js'
|
|
14
|
+
import { runMigrations } from '../lib/migrations/index.js'
|
|
14
15
|
|
|
15
16
|
const makeActor = (domain, username, shared = true) =>
|
|
16
17
|
as2.import({
|
|
@@ -63,12 +64,11 @@ describe('ActivityDistributor', () => {
|
|
|
63
64
|
before(async () => {
|
|
64
65
|
logger = Logger({ level: 'silent' })
|
|
65
66
|
formatter = new UrlFormatter('https://activitypubbot.example')
|
|
66
|
-
connection = new Sequelize('sqlite
|
|
67
|
+
connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
67
68
|
await connection.authenticate()
|
|
69
|
+
await runMigrations(connection)
|
|
68
70
|
actorStorage = new ActorStorage(connection, formatter)
|
|
69
|
-
await actorStorage.initialize()
|
|
70
71
|
keyStorage = new KeyStorage(connection, logger)
|
|
71
|
-
await keyStorage.initialize()
|
|
72
72
|
const signer = new HTTPSignature(logger)
|
|
73
73
|
const digester = new Digester(logger)
|
|
74
74
|
client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
|
|
@@ -17,6 +17,9 @@ import bots from './fixtures/bots.js'
|
|
|
17
17
|
import { nockSetup, postInbox, makeActor, nockFormat } from './utils/nock.js'
|
|
18
18
|
import { Digester } from '../lib/digester.js'
|
|
19
19
|
import { HTTPSignature } from '../lib/httpsignature.js'
|
|
20
|
+
import { runMigrations } from '../lib/migrations/index.js'
|
|
21
|
+
import { BotContext } from '../lib/botcontext.js'
|
|
22
|
+
import { Transformer } from '../lib/microsyntax.js'
|
|
20
23
|
|
|
21
24
|
describe('ActivityHandler', () => {
|
|
22
25
|
const domain = 'activitypubbot.example'
|
|
@@ -36,27 +39,46 @@ describe('ActivityHandler', () => {
|
|
|
36
39
|
let botId = null
|
|
37
40
|
const botName = 'ok'
|
|
38
41
|
let bot = null
|
|
42
|
+
const loggerBotName = 'logging'
|
|
43
|
+
let lb = null
|
|
44
|
+
let lbId = null
|
|
45
|
+
let transformer = null
|
|
39
46
|
before(async () => {
|
|
40
47
|
logger = Logger({ level: 'silent' })
|
|
41
48
|
formatter = new UrlFormatter(origin)
|
|
42
|
-
connection = new Sequelize('sqlite
|
|
49
|
+
connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
43
50
|
await connection.authenticate()
|
|
51
|
+
await runMigrations(connection)
|
|
44
52
|
botDataStorage = new BotDataStorage(connection)
|
|
45
|
-
await botDataStorage.initialize()
|
|
46
53
|
objectStorage = new ObjectStorage(connection)
|
|
47
|
-
await objectStorage.initialize()
|
|
48
54
|
keyStorage = new KeyStorage(connection, logger)
|
|
49
|
-
await keyStorage.initialize()
|
|
50
55
|
actorStorage = new ActorStorage(connection, formatter)
|
|
51
|
-
await actorStorage.initialize()
|
|
52
56
|
const signer = new HTTPSignature(logger)
|
|
53
57
|
const digester = new Digester(logger)
|
|
54
58
|
client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
|
|
55
59
|
distributor = new ActivityDistributor(client, formatter, actorStorage, logger)
|
|
56
60
|
authz = new Authorizer(actorStorage, formatter, client)
|
|
57
61
|
cache = new ObjectCache({ longTTL: 3600 * 1000, shortTTL: 300 * 1000, maxItems: 1000 })
|
|
62
|
+
transformer = new Transformer(`${origin}/tag/`, client)
|
|
63
|
+
await Promise.all(
|
|
64
|
+
Object.values(bots).map(bot => bot.initialize(
|
|
65
|
+
new BotContext(
|
|
66
|
+
bot.username,
|
|
67
|
+
botDataStorage,
|
|
68
|
+
objectStorage,
|
|
69
|
+
actorStorage,
|
|
70
|
+
client,
|
|
71
|
+
distributor,
|
|
72
|
+
formatter,
|
|
73
|
+
transformer,
|
|
74
|
+
logger
|
|
75
|
+
)
|
|
76
|
+
))
|
|
77
|
+
)
|
|
58
78
|
botId = formatter.format({ username: botName })
|
|
79
|
+
lbId = formatter.format({ username: loggerBotName })
|
|
59
80
|
bot = bots[botName]
|
|
81
|
+
lb = bots[loggerBotName]
|
|
60
82
|
await objectStorage.create(await as2.import({
|
|
61
83
|
id: formatter.format({ username: 'test1', type: 'object', nanoid: '_pEWsKke-7lACTdM3J_qd' }),
|
|
62
84
|
type: 'Object',
|
|
@@ -299,6 +321,19 @@ describe('ActivityHandler', () => {
|
|
|
299
321
|
await handler.onIdle()
|
|
300
322
|
assert.ok(!postInbox.follower3)
|
|
301
323
|
})
|
|
324
|
+
it('notifies the bot of a follow activity', async () => {
|
|
325
|
+
const actor = await makeActor('follower4')
|
|
326
|
+
const activity = await as2.import({
|
|
327
|
+
type: 'Follow',
|
|
328
|
+
id: 'https://social.example/user/follower4/follow/1',
|
|
329
|
+
actor: actor.id,
|
|
330
|
+
object: lbId,
|
|
331
|
+
to: lbId
|
|
332
|
+
})
|
|
333
|
+
await handler.handleActivity(lb, activity)
|
|
334
|
+
assert.ok(lb.follows.has(activity.id))
|
|
335
|
+
await handler.onIdle()
|
|
336
|
+
})
|
|
302
337
|
it('can handle an accept activity', async () => {
|
|
303
338
|
const actor = await makeActor('accepter1')
|
|
304
339
|
const followActivity = await as2.import({
|
|
@@ -811,6 +846,34 @@ describe('ActivityHandler', () => {
|
|
|
811
846
|
await objectStorage.isInCollection(note.id, 'likes', activity2)
|
|
812
847
|
)
|
|
813
848
|
})
|
|
849
|
+
it('notifies the bot of a like activity', async () => {
|
|
850
|
+
const actor = await makeActor('liker9')
|
|
851
|
+
const note = await as2.import({
|
|
852
|
+
attributedTo: lbId,
|
|
853
|
+
id: formatter.format({
|
|
854
|
+
username: loggerBotName,
|
|
855
|
+
type: 'note',
|
|
856
|
+
nanoid: 'IGeAucyHD-s3Ywg9X9sCo'
|
|
857
|
+
}),
|
|
858
|
+
type: 'Note',
|
|
859
|
+
content: 'Hello, world!',
|
|
860
|
+
to: 'as:Public'
|
|
861
|
+
})
|
|
862
|
+
await objectStorage.create(note)
|
|
863
|
+
const activity = await as2.import({
|
|
864
|
+
type: 'Like',
|
|
865
|
+
actor: actor.id,
|
|
866
|
+
id: nockFormat({
|
|
867
|
+
username: 'liker9',
|
|
868
|
+
type: 'Like',
|
|
869
|
+
nanoid: '3fKK6LcMtqrAp1Ekn471u'
|
|
870
|
+
}),
|
|
871
|
+
object: note.id,
|
|
872
|
+
to: [lbId, 'as:Public']
|
|
873
|
+
})
|
|
874
|
+
await handler.handleActivity(lb, activity)
|
|
875
|
+
assert.ok(lb.likes.has(activity.id))
|
|
876
|
+
})
|
|
814
877
|
it('can handle an announce activity', async () => {
|
|
815
878
|
const actor = await makeActor('announcer1')
|
|
816
879
|
const note = await as2.import({
|
|
@@ -1017,6 +1080,34 @@ describe('ActivityHandler', () => {
|
|
|
1017
1080
|
await objectStorage.isInCollection(note.id, 'shares', activity2)
|
|
1018
1081
|
)
|
|
1019
1082
|
})
|
|
1083
|
+
it('notifies the bot on an announce activity', async () => {
|
|
1084
|
+
const actor = await makeActor('announcer9')
|
|
1085
|
+
const note = await as2.import({
|
|
1086
|
+
attributedTo: lbId,
|
|
1087
|
+
id: formatter.format({
|
|
1088
|
+
username: loggerBotName,
|
|
1089
|
+
type: 'note',
|
|
1090
|
+
nanoid: 'LNCVgovrjpA6oSKnGDax2'
|
|
1091
|
+
}),
|
|
1092
|
+
type: 'Note',
|
|
1093
|
+
content: 'Hello, world!',
|
|
1094
|
+
to: 'as:Public'
|
|
1095
|
+
})
|
|
1096
|
+
await objectStorage.create(note)
|
|
1097
|
+
const activity = await as2.import({
|
|
1098
|
+
type: 'Announce',
|
|
1099
|
+
actor: actor.id,
|
|
1100
|
+
id: nockFormat({
|
|
1101
|
+
username: 'announcer9',
|
|
1102
|
+
type: 'Announce',
|
|
1103
|
+
nanoid: 'LmVvlEBHNf2X6nfgzMe6F'
|
|
1104
|
+
}),
|
|
1105
|
+
object: note.id,
|
|
1106
|
+
to: [lbId, 'as:Public']
|
|
1107
|
+
})
|
|
1108
|
+
await handler.handleActivity(lb, activity)
|
|
1109
|
+
assert.ok(lb.shares.has(activity.id))
|
|
1110
|
+
})
|
|
1020
1111
|
it('can handle a block activity', async () => {
|
|
1021
1112
|
const actor = await makeActor('blocker1')
|
|
1022
1113
|
await actorStorage.addToCollection(botName, 'followers', actor)
|