@evanp/activitypub-bot 0.8.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 +262 -12
- 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 +18 -11
- 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/tests/utils/nock.js
CHANGED
|
@@ -3,10 +3,20 @@ import nock from 'nock'
|
|
|
3
3
|
import crypto from 'node:crypto'
|
|
4
4
|
import { promisify } from 'node:util'
|
|
5
5
|
|
|
6
|
+
const PAGE_SIZE = 20
|
|
7
|
+
|
|
6
8
|
const generateKeyPair = promisify(crypto.generateKeyPair)
|
|
7
9
|
|
|
10
|
+
const defaultDomain = 'social.example'
|
|
11
|
+
|
|
8
12
|
const domains = new Map()
|
|
9
|
-
domains[
|
|
13
|
+
domains[defaultDomain] = new Map()
|
|
14
|
+
|
|
15
|
+
const graph = new Map()
|
|
16
|
+
graph[defaultDomain] = new Map()
|
|
17
|
+
|
|
18
|
+
const collections = new Map()
|
|
19
|
+
collections[defaultDomain] = new Map()
|
|
10
20
|
|
|
11
21
|
const newKeyPair = async () => {
|
|
12
22
|
return await generateKeyPair(
|
|
@@ -25,7 +35,7 @@ const newKeyPair = async () => {
|
|
|
25
35
|
)
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
export const getPair = async (username, domain =
|
|
38
|
+
export const getPair = async (username, domain = defaultDomain) => {
|
|
29
39
|
if (!domains.has(domain)) {
|
|
30
40
|
domains.set(domain, new Map())
|
|
31
41
|
}
|
|
@@ -36,17 +46,57 @@ export const getPair = async (username, domain = 'social.example') => {
|
|
|
36
46
|
return domains.get(domain).get(username)
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
export const getPublicKey = async (username, domain =
|
|
49
|
+
export const getPublicKey = async (username, domain = defaultDomain) => {
|
|
40
50
|
const pair = await getPair(username, domain)
|
|
41
51
|
return pair.publicKey
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
export const getPrivateKey = async (username, domain =
|
|
54
|
+
export const getPrivateKey = async (username, domain = defaultDomain) => {
|
|
45
55
|
const pair = await getPair(username, domain)
|
|
46
56
|
return pair.privateKey
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
function ensureGraph (domain, username) {
|
|
60
|
+
if (!graph.has(domain)) {
|
|
61
|
+
graph.set(domain, new Map())
|
|
62
|
+
}
|
|
63
|
+
if (!graph.get(domain).has(username)) {
|
|
64
|
+
graph.get(domain).set(username, new Map())
|
|
65
|
+
graph.get(domain).get(username).set('followers', [])
|
|
66
|
+
graph.get(domain).get(username).set('following', [])
|
|
67
|
+
}
|
|
68
|
+
return graph.get(domain).get(username)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function addFollower (username, id, domain = defaultDomain) {
|
|
72
|
+
ensureGraph(domain, username).get('followers').unshift(id)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function addFollowing (username, id, domain = defaultDomain) {
|
|
76
|
+
ensureGraph(domain, username).get('following').unshift(id)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ensureCollection (domain, username, collection) {
|
|
80
|
+
if (!collections.has(domain)) {
|
|
81
|
+
collections.set(domain, new Map())
|
|
82
|
+
}
|
|
83
|
+
const dc = collections.get(domain)
|
|
84
|
+
if (!dc.has(username)) {
|
|
85
|
+
dc.set(username, new Map())
|
|
86
|
+
}
|
|
87
|
+
const dcu = dc.get(username)
|
|
88
|
+
if (!dcu.has(collection)) {
|
|
89
|
+
dcu.set(collection, [])
|
|
90
|
+
}
|
|
91
|
+
const dcuc = dcu.get(collection)
|
|
92
|
+
return dcuc
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function addToCollection (username, collection, item, domain = defaultDomain) {
|
|
96
|
+
ensureCollection(domain, username, collection).unshift(item)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const nockSignature = async ({ method = 'GET', url, date, digest = null, username, domain = defaultDomain }) => {
|
|
50
100
|
const privateKey = await getPrivateKey(username, domain)
|
|
51
101
|
const keyId = nockFormat({ username, key: true, domain })
|
|
52
102
|
const parsed = new URL(url)
|
|
@@ -66,7 +116,7 @@ export const nockSignature = async ({ method = 'GET', url, date, digest = null,
|
|
|
66
116
|
return `keyId="${keyId}",headers="(request-target) host date${(digest) ? ' digest' : ''}",signature="${signature.replace(/"/g, '\\"')}",algorithm="rsa-sha256"`
|
|
67
117
|
}
|
|
68
118
|
|
|
69
|
-
export const nockSignatureFragment = async ({ method = 'GET', url, date, digest = null, username, domain =
|
|
119
|
+
export const nockSignatureFragment = async ({ method = 'GET', url, date, digest = null, username, domain = defaultDomain }) => {
|
|
70
120
|
const keyId = nockFormat({ username, domain }) + '#main-key'
|
|
71
121
|
const privateKey = await getPrivateKey(username, domain)
|
|
72
122
|
const parsed = new URL(url)
|
|
@@ -86,10 +136,10 @@ export const nockSignatureFragment = async ({ method = 'GET', url, date, digest
|
|
|
86
136
|
return `keyId="${keyId}",headers="(request-target) host date${(digest) ? ' digest' : ''}",signature="${signature.replace(/"/g, '\\"')}",algorithm="rsa-sha256"`
|
|
87
137
|
}
|
|
88
138
|
|
|
89
|
-
export const nockKeyRotate = async (username, domain =
|
|
139
|
+
export const nockKeyRotate = async (username, domain = defaultDomain) =>
|
|
90
140
|
domains.get(domain).set(username, await newKeyPair(username))
|
|
91
141
|
|
|
92
|
-
export const makeActor = async (username, domain =
|
|
142
|
+
export const makeActor = async (username, domain = defaultDomain) =>
|
|
93
143
|
await as2.import({
|
|
94
144
|
'@context': [
|
|
95
145
|
'https://www.w3.org/ns/activitystreams',
|
|
@@ -124,7 +174,8 @@ export async function makeObject (
|
|
|
124
174
|
username,
|
|
125
175
|
type,
|
|
126
176
|
num,
|
|
127
|
-
domain =
|
|
177
|
+
domain = defaultDomain,
|
|
178
|
+
extra = {}) {
|
|
128
179
|
const props = {
|
|
129
180
|
'@context': [
|
|
130
181
|
'https://www.w3.org/ns/activitystreams',
|
|
@@ -133,7 +184,8 @@ export async function makeObject (
|
|
|
133
184
|
],
|
|
134
185
|
id: nockFormat({ username, type, num, domain }),
|
|
135
186
|
type: uppercase(type),
|
|
136
|
-
to: 'as:Public'
|
|
187
|
+
to: 'as:Public',
|
|
188
|
+
...extra
|
|
137
189
|
}
|
|
138
190
|
if (isActivityType(type)) {
|
|
139
191
|
props.actor = nockFormat({ username, domain })
|
|
@@ -149,7 +201,7 @@ export async function makeObject (
|
|
|
149
201
|
return as2.import(props)
|
|
150
202
|
}
|
|
151
203
|
|
|
152
|
-
export const makeTransitive = (username, type, num, obj, domain =
|
|
204
|
+
export const makeTransitive = (username, type, num, obj, domain = defaultDomain) =>
|
|
153
205
|
as2.import({
|
|
154
206
|
id: nockFormat({ username, type, num, obj, domain }),
|
|
155
207
|
type: uppercase(type),
|
|
@@ -159,7 +211,7 @@ export const makeTransitive = (username, type, num, obj, domain = 'social.exampl
|
|
|
159
211
|
})
|
|
160
212
|
|
|
161
213
|
const uppercase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
|
|
162
|
-
const lowercase = (str) => str.
|
|
214
|
+
const lowercase = (str) => str.toLowerCase()
|
|
163
215
|
|
|
164
216
|
export const postInbox = {}
|
|
165
217
|
|
|
@@ -169,10 +221,28 @@ export const resetInbox = () => {
|
|
|
169
221
|
}
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
|
|
224
|
+
const requestHeaders = new Map()
|
|
225
|
+
|
|
226
|
+
export function getRequestHeaders (uri) {
|
|
227
|
+
return requestHeaders.get(uri)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const resetRequestHeaders = () => {
|
|
231
|
+
requestHeaders.clear()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const captureRequestHeaders = (domain, uri, req) => {
|
|
235
|
+
const url = new URL(uri, `https://${domain}`).toString()
|
|
236
|
+
const headers = req?.headers || {}
|
|
237
|
+
requestHeaders.set(url, headers)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const nockSetup = (domain, logger = null) =>
|
|
173
241
|
nock(`https://${domain}`)
|
|
242
|
+
.persist()
|
|
174
243
|
.get(/^\/.well-known\/webfinger/)
|
|
175
|
-
.reply(async (uri, requestBody)
|
|
244
|
+
.reply(async function (uri, requestBody) {
|
|
245
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
176
246
|
const parsed = new URL(uri, `https://${domain}`)
|
|
177
247
|
const resource = parsed.searchParams.get('resource')
|
|
178
248
|
if (!resource) {
|
|
@@ -194,17 +264,18 @@ export const nockSetup = (domain) =>
|
|
|
194
264
|
{ 'Content-Type': 'application/jrd+json' }]
|
|
195
265
|
})
|
|
196
266
|
.get(/^\/user\/(\w+)$/)
|
|
197
|
-
.reply(async (uri, requestBody)
|
|
267
|
+
.reply(async function (uri, requestBody) {
|
|
268
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
198
269
|
const username = uri.match(/^\/user\/(\w+)$/)[1]
|
|
199
270
|
const actor = await makeActor(username, domain)
|
|
200
|
-
const actorText = await actor.
|
|
271
|
+
const actorText = await actor.write(
|
|
201
272
|
{ additional_context: 'https://w3id.org/security/v1' }
|
|
202
273
|
)
|
|
203
274
|
return [200, actorText, { 'Content-Type': 'application/activity+json' }]
|
|
204
275
|
})
|
|
205
|
-
.persist()
|
|
206
276
|
.post(/^\/user\/(\w+)\/inbox$/)
|
|
207
|
-
.reply(async (uri, requestBody)
|
|
277
|
+
.reply(async function (uri, requestBody) {
|
|
278
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
208
279
|
const username = uri.match(/^\/user\/(\w+)\/inbox$/)[1]
|
|
209
280
|
if (username in postInbox) {
|
|
210
281
|
postInbox[username] += 1
|
|
@@ -213,9 +284,13 @@ export const nockSetup = (domain) =>
|
|
|
213
284
|
}
|
|
214
285
|
return [202, 'accepted']
|
|
215
286
|
})
|
|
216
|
-
.
|
|
287
|
+
.get(/^\/user\/(\w+)\/inbox$/)
|
|
288
|
+
.reply(async function (uri, requestBody) {
|
|
289
|
+
return [403, 'forbidden']
|
|
290
|
+
})
|
|
217
291
|
.get(/^\/user\/(\w+)\/publickey$/)
|
|
218
|
-
.reply(async (uri, requestBody)
|
|
292
|
+
.reply(async function (uri, requestBody) {
|
|
293
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
219
294
|
const username = uri.match(/^\/user\/(\w+)\/publickey$/)[1]
|
|
220
295
|
const publicKey = await as2.import({
|
|
221
296
|
'@context': [
|
|
@@ -227,25 +302,169 @@ export const nockSetup = (domain) =>
|
|
|
227
302
|
type: 'CryptographicKey',
|
|
228
303
|
publicKeyPem: await getPublicKey(username, domain)
|
|
229
304
|
})
|
|
230
|
-
const publicKeyText = await publicKey.
|
|
305
|
+
const publicKeyText = await publicKey.write(
|
|
231
306
|
{ additional_context: 'https://w3id.org/security/v1' }
|
|
232
307
|
)
|
|
233
308
|
return [200, publicKeyText, { 'Content-Type': 'application/activity+json' }]
|
|
234
309
|
})
|
|
235
|
-
.
|
|
310
|
+
.get(/^\/user\/(\w+)\/followers$/)
|
|
311
|
+
.reply(async function (uri, requestBody) {
|
|
312
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
313
|
+
const username = uri.match(/^\/user\/(\w+)\/followers$/)[1]
|
|
314
|
+
const items = ensureGraph(domain, username).get('followers')
|
|
315
|
+
const followers = await as2.import({
|
|
316
|
+
'@context': [
|
|
317
|
+
'https://www.w3.org/ns/activitystreams',
|
|
318
|
+
'https://w3id.org/fep/5711'
|
|
319
|
+
],
|
|
320
|
+
id: `https://${domain}/user/${username}/followers`,
|
|
321
|
+
attributedTo: `https://${domain}/user/${username}`,
|
|
322
|
+
to: 'as:Public',
|
|
323
|
+
followersOf: `https://${domain}/user/${username}`,
|
|
324
|
+
type: 'OrderedCollection',
|
|
325
|
+
totalItems: items.length,
|
|
326
|
+
items
|
|
327
|
+
})
|
|
328
|
+
const followersText = await followers.write(
|
|
329
|
+
{ additional_context: 'https://w3id.org/fep/5711' }
|
|
330
|
+
)
|
|
331
|
+
return [200, followersText, { 'Content-Type': 'application/activity+json' }]
|
|
332
|
+
})
|
|
333
|
+
.get(/^\/user\/(\w+)\/following$/)
|
|
334
|
+
.reply(async function (uri, requestBody) {
|
|
335
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
336
|
+
const username = uri.match(/^\/user\/(\w+)\/following$/)[1]
|
|
337
|
+
const items = ensureGraph(domain, username).get('following')
|
|
338
|
+
const following = await as2.import({
|
|
339
|
+
'@context': [
|
|
340
|
+
'https://www.w3.org/ns/activitystreams',
|
|
341
|
+
'https://w3id.org/fep/5711'
|
|
342
|
+
],
|
|
343
|
+
id: `https://${domain}/user/${username}/following`,
|
|
344
|
+
attributedTo: `https://${domain}/user/${username}`,
|
|
345
|
+
to: 'as:Public',
|
|
346
|
+
followersOf: `https://${domain}/user/${username}`,
|
|
347
|
+
type: 'OrderedCollection',
|
|
348
|
+
totalItems: items.length,
|
|
349
|
+
items
|
|
350
|
+
})
|
|
351
|
+
const followingText = await following.write(
|
|
352
|
+
{ additional_context: 'https://w3id.org/fep/5711' }
|
|
353
|
+
)
|
|
354
|
+
return [200, followingText, { 'Content-Type': 'application/activity+json' }]
|
|
355
|
+
})
|
|
356
|
+
.get(/^\/user\/(\w+)\/collection\/(\d+)$/)
|
|
357
|
+
.reply(async function (uri, requestBody) {
|
|
358
|
+
logger?.debug('Capturing request headers')
|
|
359
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
360
|
+
logger?.debug('Matching parameters')
|
|
361
|
+
const match = uri.match(/^\/user\/(\w+)\/collection\/(\d+)$/)
|
|
362
|
+
const username = match[1]
|
|
363
|
+
const type = 'Collection'
|
|
364
|
+
const num = parseInt(match[2])
|
|
365
|
+
logger?.debug('Ensuring collection')
|
|
366
|
+
const items = ensureCollection(domain, username, num)
|
|
367
|
+
logger?.debug('Making collection object')
|
|
368
|
+
const summary = `${num} collection by ${username}`
|
|
369
|
+
const obj = await makeObject(username, type, num, domain, { items, summary })
|
|
370
|
+
logger?.debug('Writing collection object to text')
|
|
371
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
372
|
+
logger?.debug('Sending output')
|
|
373
|
+
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
374
|
+
})
|
|
375
|
+
.get(/^\/user\/(\w+)\/orderedcollection\/(\d+)$/)
|
|
376
|
+
.reply(async function (uri, requestBody) {
|
|
377
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
378
|
+
const match = uri.match(/^\/user\/(\w+)\/orderedcollection\/(\d+)$/)
|
|
379
|
+
const username = match[1]
|
|
380
|
+
const type = 'OrderedCollection'
|
|
381
|
+
const num = parseInt(match[2])
|
|
382
|
+
const orderedItems = ensureCollection(domain, username, num)
|
|
383
|
+
const summary = `${num} ordered collection by ${username}`
|
|
384
|
+
const obj = await makeObject(username, type, num, domain, { orderedItems, summary })
|
|
385
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
386
|
+
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
387
|
+
})
|
|
388
|
+
.get(/^\/user\/(\w+)\/pagedcollection\/(\d+)$/)
|
|
389
|
+
.reply(async function (uri, requestBody) {
|
|
390
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
391
|
+
const match = uri.match(/^\/user\/(\w+)\/pagedcollection\/(\d+)$/)
|
|
392
|
+
const username = match[1]
|
|
393
|
+
const type = 'Collection'
|
|
394
|
+
const num = parseInt(match[2])
|
|
395
|
+
const items = ensureCollection(domain, username, num)
|
|
396
|
+
const totalItems = items.length
|
|
397
|
+
const first = (totalItems > 0)
|
|
398
|
+
? nockFormat({ username, domain, type: 'PagedCollection', num, page: 0 })
|
|
399
|
+
: undefined
|
|
400
|
+
const obj = await makeObject(username, type, num, domain, { totalItems, first })
|
|
401
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
402
|
+
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
403
|
+
})
|
|
404
|
+
.get(/^\/user\/(\w+)\/pagedcollection\/(\d+)\/page\/(\d+)$/)
|
|
405
|
+
.reply(async function (uri, requestBody) {
|
|
406
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
407
|
+
const match = uri.match(/^\/user\/(\w+)\/pagedcollection\/(\d+)\/page\/(\d+)$/)
|
|
408
|
+
const username = match[1]
|
|
409
|
+
const type = 'CollectionPage'
|
|
410
|
+
const num = parseInt(match[2])
|
|
411
|
+
const page = parseInt(match[3])
|
|
412
|
+
const allItems = ensureCollection(domain, username, num)
|
|
413
|
+
const items = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
|
|
414
|
+
const next = (allItems.length > (page + 1) * PAGE_SIZE)
|
|
415
|
+
? nockFormat({ username, domain, type: 'PagedCollection', num, page: page + 1 })
|
|
416
|
+
: undefined
|
|
417
|
+
const obj = await makeObject(username, type, num, domain, { items, next })
|
|
418
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
419
|
+
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
420
|
+
})
|
|
421
|
+
.get(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)$/)
|
|
422
|
+
.reply(async function (uri, requestBody) {
|
|
423
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
424
|
+
const match = uri.match(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)$/)
|
|
425
|
+
const username = match[1]
|
|
426
|
+
const type = 'OrderedCollection'
|
|
427
|
+
const num = parseInt(match[2])
|
|
428
|
+
const items = ensureCollection(domain, username, num)
|
|
429
|
+
const totalItems = items.length
|
|
430
|
+
const first = (totalItems > 0)
|
|
431
|
+
? nockFormat({ username, domain, type: 'PagedOrderedCollection', num, page: 0 })
|
|
432
|
+
: undefined
|
|
433
|
+
const obj = await makeObject(username, type, num, domain, { totalItems, first })
|
|
434
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
435
|
+
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
436
|
+
})
|
|
437
|
+
.get(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)\/page\/(\d+)$/)
|
|
438
|
+
.reply(async function (uri, requestBody) {
|
|
439
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
440
|
+
const match = uri.match(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)\/page\/(\d+)$/)
|
|
441
|
+
const username = match[1]
|
|
442
|
+
const type = 'OrderedCollectionPage'
|
|
443
|
+
const num = parseInt(match[2])
|
|
444
|
+
const page = parseInt(match[3])
|
|
445
|
+
const allItems = ensureCollection(domain, username, num)
|
|
446
|
+
const orderedItems = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
|
|
447
|
+
const next = (allItems.length > (page + 1) * PAGE_SIZE)
|
|
448
|
+
? nockFormat({ username, domain, type: 'PagedOrderedCollection', num, page: page + 1 })
|
|
449
|
+
: undefined
|
|
450
|
+
const obj = await makeObject(username, type, num, domain, { orderedItems, next })
|
|
451
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
452
|
+
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
453
|
+
})
|
|
236
454
|
.get(/^\/user\/(\w+)\/(\w+)\/(\d+)$/)
|
|
237
|
-
.reply(async (uri, requestBody)
|
|
455
|
+
.reply(async function (uri, requestBody) {
|
|
456
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
238
457
|
const match = uri.match(/^\/user\/(\w+)\/(\w+)\/(\d+)$/)
|
|
239
458
|
const username = match[1]
|
|
240
459
|
const type = uppercase(match[2])
|
|
241
460
|
const num = match[3]
|
|
242
461
|
const obj = await makeObject(username, type, num, domain)
|
|
243
|
-
const objText = await obj.
|
|
462
|
+
const objText = await obj.write({ useOriginalContext: true })
|
|
244
463
|
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
245
464
|
})
|
|
246
|
-
.persist()
|
|
247
465
|
.get(/^\/user\/(\w+)\/(\w+)\/(\d+)\/(.*)$/)
|
|
248
|
-
.reply(async (uri, requestBody)
|
|
466
|
+
.reply(async function (uri, requestBody) {
|
|
467
|
+
captureRequestHeaders(domain, uri, this?.req)
|
|
249
468
|
const match = uri.match(/^\/user\/(\w+)\/(\w+)\/(\d+)\/(.*)$/)
|
|
250
469
|
const username = match[1]
|
|
251
470
|
const type = match[2]
|
|
@@ -256,10 +475,12 @@ export const nockSetup = (domain) =>
|
|
|
256
475
|
return [200, actText, { 'Content-Type': 'application/activity+json' }]
|
|
257
476
|
})
|
|
258
477
|
|
|
259
|
-
export function nockFormat ({ username, type, num, obj, key, domain =
|
|
478
|
+
export function nockFormat ({ username, type, num, obj, key, collection, page, domain = defaultDomain }) {
|
|
260
479
|
let url = `https://${domain}/user/${username}`
|
|
261
480
|
if (key) {
|
|
262
481
|
url = `${url}/publickey`
|
|
482
|
+
} else if (collection) {
|
|
483
|
+
url = `${url}/${collection}`
|
|
263
484
|
} else {
|
|
264
485
|
if (type && num) {
|
|
265
486
|
url = `${url}/${lowercase(type)}/${num}`
|
|
@@ -269,6 +490,8 @@ export function nockFormat ({ username, type, num, obj, key, domain = 'social.ex
|
|
|
269
490
|
} else {
|
|
270
491
|
url = `${url}/${obj}`
|
|
271
492
|
}
|
|
493
|
+
} else if (typeof page === 'number') {
|
|
494
|
+
url = `${url}/page/${page}`
|
|
272
495
|
}
|
|
273
496
|
}
|
|
274
497
|
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
name: Build and push Docker image
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
push:
|
|
6
|
-
branches:
|
|
7
|
-
- main
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
packages: write
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
build:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
|
-
env:
|
|
18
|
-
REGISTRY: ghcr.io
|
|
19
|
-
REPOSITORY: evanp/activitypub-bot
|
|
20
|
-
|
|
21
|
-
steps:
|
|
22
|
-
- name: Checkout repository
|
|
23
|
-
uses: actions/checkout@v2
|
|
24
|
-
|
|
25
|
-
- name: Set up Docker Buildx
|
|
26
|
-
uses: docker/setup-buildx-action@v1
|
|
27
|
-
|
|
28
|
-
- name: Log in to GitHub Container Registry
|
|
29
|
-
uses: docker/login-action@v1
|
|
30
|
-
with:
|
|
31
|
-
registry: ${{ env.REGISTRY }}
|
|
32
|
-
username: ${{ github.actor }}
|
|
33
|
-
password: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
-
|
|
35
|
-
- name: Build and push
|
|
36
|
-
uses: docker/build-push-action@v5
|
|
37
|
-
with:
|
|
38
|
-
context: .
|
|
39
|
-
push: true
|
|
40
|
-
platforms: linux/amd64,linux/arm64
|
|
41
|
-
tags: |
|
|
42
|
-
${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest
|
|
43
|
-
${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }}
|
|
44
|
-
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest
|
|
45
|
-
cache-to: type=inline
|
package/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { makeApp } from './lib/app.js'
|
|
2
|
-
|
|
3
|
-
const DATABASE_URL = process.env.DATABASE_URL || 'sqlite::memory:'
|
|
4
|
-
const ORIGIN = process.env.ORIGIN || 'https://activitypubbot.test'
|
|
5
|
-
const PORT = process.env.PORT || 9000 // HAL
|
|
6
|
-
const BOTS_CONFIG_FILE = process.env.BOTS_CONFIG_FILE || './bots/index.js'
|
|
7
|
-
const LOG_LEVEL = process.env.LOG_LEVEL || (process.env.NODE_ENV === 'test' ? 'silent' : 'info')
|
|
8
|
-
|
|
9
|
-
const bots = (await import(BOTS_CONFIG_FILE)).default
|
|
10
|
-
|
|
11
|
-
const app = await makeApp(DATABASE_URL, ORIGIN, bots, LOG_LEVEL)
|
|
12
|
-
|
|
13
|
-
const server = app.listen(parseInt(PORT), () => {
|
|
14
|
-
app.locals.logger.info(`Listening on port ${PORT}`)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
process.on('SIGTERM', () => {
|
|
18
|
-
console.log('Received SIGTERM')
|
|
19
|
-
server.close(async () => {
|
|
20
|
-
await app.cleanup()
|
|
21
|
-
process.exit(0)
|
|
22
|
-
})
|
|
23
|
-
})
|