@evanp/activitypub-bot 0.28.7 → 0.29.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/lib/activitystreams.js +11 -0
- package/lib/routes/server.js +6 -1
- package/lib/routes/user.js +7 -2
- package/lib/routes/webfinger.js +83 -25
- package/lib/urlformatter.js +10 -1
- package/package.json +1 -1
package/lib/activitystreams.js
CHANGED
|
@@ -69,4 +69,15 @@ as2.registerContext('https://purl.archive.org/socialweb/thread/1.0', {
|
|
|
69
69
|
}
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
+
as2.registerContext('https://purl.archive.org/socialweb/webfinger', {
|
|
73
|
+
'@context': {
|
|
74
|
+
wf: 'https://purl.archive.org/socialweb/webfinger#',
|
|
75
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
76
|
+
webfinger: {
|
|
77
|
+
'@id': 'wf:webfinger',
|
|
78
|
+
'@type': 'xsd:string'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
72
83
|
export default as2
|
package/lib/routes/server.js
CHANGED
|
@@ -13,14 +13,19 @@ router.get('/actor', async (req, res) => {
|
|
|
13
13
|
const homepage = `${req.protocol}://${req.get('host')}/`
|
|
14
14
|
const { formatter, keyStorage } = req.app.locals
|
|
15
15
|
const publicKeyPem = await keyStorage.getPublicKey(null)
|
|
16
|
+
const acct = formatter.acct()
|
|
17
|
+
const webfinger = acct.slice(5)
|
|
16
18
|
const server = await as2.import({
|
|
17
19
|
'@context': [
|
|
18
20
|
'https://www.w3.org/ns/activitystreams',
|
|
19
|
-
'https://w3id.org/security/v1'
|
|
21
|
+
'https://w3id.org/security/v1',
|
|
22
|
+
'https://purl.archive.org/socialweb/webfinger'
|
|
20
23
|
],
|
|
21
24
|
id: formatter.format({ server: true }),
|
|
22
25
|
type: 'Service',
|
|
23
26
|
to: 'as:Public',
|
|
27
|
+
alsoKnownAs: acct,
|
|
28
|
+
webfinger,
|
|
24
29
|
publicKey: {
|
|
25
30
|
publicKeyPem,
|
|
26
31
|
id: formatter.format({ server: true, type: 'publickey' }),
|
package/lib/routes/user.js
CHANGED
|
@@ -13,13 +13,18 @@ router.get('/user/:username', async (req, res, next) => {
|
|
|
13
13
|
return next(createHttpError(404, `User ${username} not found`))
|
|
14
14
|
}
|
|
15
15
|
const publicKeyPem = await keyStorage.getPublicKey(username)
|
|
16
|
+
const acct = formatter.acct(username)
|
|
17
|
+
const wf = acct.slice(5)
|
|
16
18
|
const actor = await actorStorage.getActor(username, {
|
|
17
19
|
'@context': [
|
|
18
20
|
'https://www.w3.org/ns/activitystreams',
|
|
19
|
-
'https://w3id.org/security/v1'
|
|
21
|
+
'https://w3id.org/security/v1',
|
|
22
|
+
'https://purl.archive.org/socialweb/webfinger'
|
|
20
23
|
],
|
|
21
24
|
name: bot.fullname,
|
|
22
25
|
summary: bot.description,
|
|
26
|
+
webfinger: wf,
|
|
27
|
+
alsoKnownAs: acct,
|
|
23
28
|
publicKey: {
|
|
24
29
|
publicKeyPem,
|
|
25
30
|
id: formatter.format({ username, type: 'publickey' }),
|
|
@@ -34,7 +39,7 @@ router.get('/user/:username', async (req, res, next) => {
|
|
|
34
39
|
res.status(200)
|
|
35
40
|
res.type(as2.mediaType)
|
|
36
41
|
const body = await actor.prettyWrite(
|
|
37
|
-
{
|
|
42
|
+
{ useOriginalContext: true }
|
|
38
43
|
)
|
|
39
44
|
res.end(body)
|
|
40
45
|
})
|
package/lib/routes/webfinger.js
CHANGED
|
@@ -1,45 +1,103 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
|
|
1
3
|
import { Router } from 'express'
|
|
2
4
|
import createHttpError from 'http-errors'
|
|
3
5
|
|
|
6
|
+
import BotMaker from '../botmaker.js'
|
|
7
|
+
|
|
4
8
|
const router = Router()
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (resource.substring(0, 5) !== 'acct:') {
|
|
12
|
-
return next(createHttpError(400, 'only acct: protocol supported'))
|
|
13
|
-
}
|
|
14
|
-
const [username, domain] = resource.substring(5).split('@')
|
|
15
|
-
if (!username || !domain) {
|
|
16
|
-
return next(createHttpError(400, 'Invalid resource parameter'))
|
|
17
|
-
}
|
|
18
|
-
const { host } = new URL(req.app.locals.origin)
|
|
19
|
-
if (domain !== host) {
|
|
20
|
-
return next(createHttpError(400, 'Invalid domain in resource parameter'))
|
|
10
|
+
async function botWebfinger (username, req, res, next) {
|
|
11
|
+
const { formatter, bots } = req.app.locals
|
|
12
|
+
const bot = await BotMaker.makeBot(bots, username)
|
|
13
|
+
if (!bot) {
|
|
14
|
+
return next(createHttpError(404, `No such bot '${username}'`))
|
|
21
15
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
res.status(200)
|
|
17
|
+
res.type('application/jrd+json')
|
|
18
|
+
res.json({
|
|
19
|
+
subject: formatter.acct(username),
|
|
20
|
+
aliases: [formatter.format({ username })],
|
|
21
|
+
links: [
|
|
22
|
+
{
|
|
23
|
+
rel: 'self',
|
|
24
|
+
type: 'application/activity+json',
|
|
25
|
+
href: formatter.format({ username })
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
]
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function serverWebfinger (req, res, next) {
|
|
32
|
+
const { formatter } = req.app.locals
|
|
33
|
+
const webfinger = formatter.acct()
|
|
34
|
+
const id = formatter.format({ server: true })
|
|
31
35
|
res.status(200)
|
|
32
36
|
res.type('application/jrd+json')
|
|
33
37
|
res.json({
|
|
34
|
-
subject:
|
|
38
|
+
subject: webfinger,
|
|
35
39
|
links: [
|
|
36
40
|
{
|
|
37
41
|
rel: 'self',
|
|
38
42
|
type: 'application/activity+json',
|
|
39
|
-
href:
|
|
43
|
+
href: id
|
|
40
44
|
}
|
|
41
45
|
]
|
|
42
46
|
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function httpsWebfinger (resource, req, res, next) {
|
|
50
|
+
const { formatter } = req.app.locals
|
|
51
|
+
assert.ok(formatter)
|
|
52
|
+
if (!formatter.isLocal(resource)) {
|
|
53
|
+
return next(createHttpError(400, 'Only local URLs'))
|
|
54
|
+
}
|
|
55
|
+
const parts = formatter.unformat(resource)
|
|
56
|
+
if (parts.server && !parts.type) {
|
|
57
|
+
return await serverWebfinger(req, res, next)
|
|
58
|
+
} else if (
|
|
59
|
+
!parts.server && parts.username && !parts.type && !parts.collection
|
|
60
|
+
) {
|
|
61
|
+
return await botWebfinger(parts.username, req, res, next)
|
|
62
|
+
} else {
|
|
63
|
+
return next(createHttpError(400, `No webfinger lookup for url ${resource}`))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function acctWebfinger (resource, req, res, next) {
|
|
68
|
+
const [username, domain] = resource.substring(5).split('@')
|
|
69
|
+
if (!username || !domain) {
|
|
70
|
+
return next(createHttpError(400, `Invalid resource parameter ${resource}`))
|
|
71
|
+
}
|
|
72
|
+
const { host } = new URL(req.app.locals.origin)
|
|
73
|
+
if (domain !== host) {
|
|
74
|
+
return next(createHttpError(400, `Invalid domain ${domain} in resource parameter`))
|
|
75
|
+
}
|
|
76
|
+
if (username === domain) {
|
|
77
|
+
return await serverWebfinger(req, res, next)
|
|
78
|
+
} else {
|
|
79
|
+
return await botWebfinger(username, req, res, next)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
router.get('/.well-known/webfinger', async (req, res, next) => {
|
|
84
|
+
const { resource } = req.query
|
|
85
|
+
if (!resource) {
|
|
86
|
+
return next(createHttpError(400, 'resource parameter is required'))
|
|
87
|
+
}
|
|
88
|
+
const colon = resource.indexOf(':')
|
|
89
|
+
const protocol = resource.slice(0, colon)
|
|
90
|
+
|
|
91
|
+
switch (protocol) {
|
|
92
|
+
case 'acct':
|
|
93
|
+
return await acctWebfinger(resource, req, res, next)
|
|
94
|
+
case 'https':
|
|
95
|
+
return await httpsWebfinger(resource, req, res, next)
|
|
96
|
+
default:
|
|
97
|
+
return next(
|
|
98
|
+
createHttpError(400, `Unsupported resource protocol '${protocol}'`)
|
|
99
|
+
)
|
|
100
|
+
}
|
|
43
101
|
})
|
|
44
102
|
|
|
45
103
|
export default router
|
package/lib/urlformatter.js
CHANGED
|
@@ -4,8 +4,11 @@ const USER_COLLECTIONS = ['inbox', 'outbox', 'liked', 'followers', 'following']
|
|
|
4
4
|
|
|
5
5
|
export class UrlFormatter {
|
|
6
6
|
#origin = null
|
|
7
|
+
#hostname
|
|
8
|
+
|
|
7
9
|
constructor (origin) {
|
|
8
10
|
this.#origin = origin
|
|
11
|
+
this.#hostname = (new URL(origin)).hostname
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
format ({ username, type, nanoid, collection, page, server }) {
|
|
@@ -75,7 +78,7 @@ export class UrlFormatter {
|
|
|
75
78
|
if (pathParts.length > 0) {
|
|
76
79
|
if (USER_COLLECTIONS.includes(pathParts[0])) {
|
|
77
80
|
parts.collection = pathParts[0]
|
|
78
|
-
} else {
|
|
81
|
+
} else if (pathParts[0] !== 'actor') {
|
|
79
82
|
parts.type = pathParts[0]
|
|
80
83
|
}
|
|
81
84
|
}
|
|
@@ -102,4 +105,10 @@ export class UrlFormatter {
|
|
|
102
105
|
const parts = this.unformat(url)
|
|
103
106
|
return (parts.username && !parts.type && !parts.collection)
|
|
104
107
|
}
|
|
108
|
+
|
|
109
|
+
acct (username = null) {
|
|
110
|
+
return (username)
|
|
111
|
+
? `acct:${username}@${this.#hostname}`
|
|
112
|
+
: `acct:${this.#hostname}@${this.#hostname}`
|
|
113
|
+
}
|
|
105
114
|
}
|