@evanp/activitypub-bot 0.13.0 → 0.13.2
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/package.json +2 -2
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/main.yml +0 -34
- package/.github/workflows/tag.yml +0 -106
- package/.nvmrc +0 -1
- package/Dockerfile +0 -17
- package/docs/activitypub.bot.drawio +0 -110
- package/tests/activitydistributor.test.js +0 -606
- package/tests/activityhandler.test.js +0 -2276
- package/tests/activitypubclient.test.js +0 -210
- package/tests/actorstorage.test.js +0 -283
- package/tests/app.test.js +0 -17
- package/tests/authorizer.test.js +0 -301
- package/tests/bot.donothing.test.js +0 -30
- package/tests/bot.ok.test.js +0 -101
- package/tests/botcontext.test.js +0 -720
- package/tests/botdatastorage.test.js +0 -88
- package/tests/botfactory.provincebotfactory.test.js +0 -430
- package/tests/digester.test.js +0 -56
- package/tests/fixtures/bots.js +0 -27
- package/tests/fixtures/eventloggingbot.js +0 -57
- package/tests/fixtures/provincebotfactory.js +0 -53
- package/tests/httpsignature.test.js +0 -199
- package/tests/httpsignatureauthenticator.test.js +0 -463
- package/tests/index.test.js +0 -12
- package/tests/keystorage.test.js +0 -124
- package/tests/microsyntax.test.js +0 -123
- package/tests/objectcache.test.js +0 -133
- package/tests/objectstorage.test.js +0 -149
- package/tests/remotekeystorage.test.js +0 -78
- package/tests/routes.actor.test.js +0 -214
- package/tests/routes.collection.test.js +0 -310
- package/tests/routes.health.test.js +0 -41
- package/tests/routes.inbox.test.js +0 -216
- package/tests/routes.object.test.js +0 -525
- package/tests/routes.server.test.js +0 -69
- package/tests/routes.sharedinbox.test.js +0 -473
- package/tests/routes.webfinger.test.js +0 -68
- package/tests/urlformatter.test.js +0 -164
- package/tests/utils/digest.js +0 -7
- package/tests/utils/nock.js +0 -499
package/tests/utils/digest.js
DELETED
package/tests/utils/nock.js
DELETED
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import as2 from '../../lib/activitystreams.js'
|
|
2
|
-
import nock from 'nock'
|
|
3
|
-
import crypto from 'node:crypto'
|
|
4
|
-
import { promisify } from 'node:util'
|
|
5
|
-
|
|
6
|
-
const PAGE_SIZE = 20
|
|
7
|
-
|
|
8
|
-
const generateKeyPair = promisify(crypto.generateKeyPair)
|
|
9
|
-
|
|
10
|
-
const defaultDomain = 'social.example'
|
|
11
|
-
|
|
12
|
-
const domains = new Map()
|
|
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()
|
|
20
|
-
|
|
21
|
-
const newKeyPair = async () => {
|
|
22
|
-
return await generateKeyPair(
|
|
23
|
-
'rsa',
|
|
24
|
-
{
|
|
25
|
-
modulusLength: 2048,
|
|
26
|
-
privateKeyEncoding: {
|
|
27
|
-
type: 'pkcs8',
|
|
28
|
-
format: 'pem'
|
|
29
|
-
},
|
|
30
|
-
publicKeyEncoding: {
|
|
31
|
-
type: 'spki',
|
|
32
|
-
format: 'pem'
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const getPair = async (username, domain = defaultDomain) => {
|
|
39
|
-
if (!domains.has(domain)) {
|
|
40
|
-
domains.set(domain, new Map())
|
|
41
|
-
}
|
|
42
|
-
if (!domains.get(domain).has(username)) {
|
|
43
|
-
const pair = await newKeyPair(username)
|
|
44
|
-
domains.get(domain).set(username, pair)
|
|
45
|
-
}
|
|
46
|
-
return domains.get(domain).get(username)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const getPublicKey = async (username, domain = defaultDomain) => {
|
|
50
|
-
const pair = await getPair(username, domain)
|
|
51
|
-
return pair.publicKey
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const getPrivateKey = async (username, domain = defaultDomain) => {
|
|
55
|
-
const pair = await getPair(username, domain)
|
|
56
|
-
return pair.privateKey
|
|
57
|
-
}
|
|
58
|
-
|
|
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 }) => {
|
|
100
|
-
const privateKey = await getPrivateKey(username, domain)
|
|
101
|
-
const keyId = nockFormat({ username, key: true, domain })
|
|
102
|
-
const parsed = new URL(url)
|
|
103
|
-
const target = (parsed.search && parsed.search.length)
|
|
104
|
-
? `${parsed.pathname}${parsed.search}`
|
|
105
|
-
: `${parsed.pathname}`
|
|
106
|
-
let data = `(request-target): ${method.toLowerCase()} ${target}\n`
|
|
107
|
-
data += `host: ${parsed.host}\n`
|
|
108
|
-
data += `date: ${date}`
|
|
109
|
-
if (digest) {
|
|
110
|
-
data += `\ndigest: ${digest}`
|
|
111
|
-
}
|
|
112
|
-
const signer = crypto.createSign('sha256')
|
|
113
|
-
signer.update(data)
|
|
114
|
-
const signature = signer.sign(privateKey).toString('base64')
|
|
115
|
-
signer.end()
|
|
116
|
-
return `keyId="${keyId}",headers="(request-target) host date${(digest) ? ' digest' : ''}",signature="${signature.replace(/"/g, '\\"')}",algorithm="rsa-sha256"`
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export const nockSignatureFragment = async ({ method = 'GET', url, date, digest = null, username, domain = defaultDomain }) => {
|
|
120
|
-
const keyId = nockFormat({ username, domain }) + '#main-key'
|
|
121
|
-
const privateKey = await getPrivateKey(username, domain)
|
|
122
|
-
const parsed = new URL(url)
|
|
123
|
-
const target = (parsed.search && parsed.search.length)
|
|
124
|
-
? `${parsed.pathname}?${parsed.search}`
|
|
125
|
-
: `${parsed.pathname}`
|
|
126
|
-
let data = `(request-target): ${method.toLowerCase()} ${target}\n`
|
|
127
|
-
data += `host: ${parsed.host}\n`
|
|
128
|
-
data += `date: ${date}`
|
|
129
|
-
if (digest) {
|
|
130
|
-
data += `\ndigest: ${digest}`
|
|
131
|
-
}
|
|
132
|
-
const signer = crypto.createSign('sha256')
|
|
133
|
-
signer.update(data)
|
|
134
|
-
const signature = signer.sign(privateKey).toString('base64')
|
|
135
|
-
signer.end()
|
|
136
|
-
return `keyId="${keyId}",headers="(request-target) host date${(digest) ? ' digest' : ''}",signature="${signature.replace(/"/g, '\\"')}",algorithm="rsa-sha256"`
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const nockKeyRotate = async (username, domain = defaultDomain) =>
|
|
140
|
-
domains.get(domain).set(username, await newKeyPair(username))
|
|
141
|
-
|
|
142
|
-
export const makeActor = async (username, domain = defaultDomain) =>
|
|
143
|
-
await as2.import({
|
|
144
|
-
'@context': [
|
|
145
|
-
'https://www.w3.org/ns/activitystreams',
|
|
146
|
-
'https://w3id.org/security/v1'
|
|
147
|
-
],
|
|
148
|
-
id: `https://${domain}/user/${username}`,
|
|
149
|
-
type: 'Person',
|
|
150
|
-
preferredUsername: username,
|
|
151
|
-
inbox: `https://${domain}/user/${username}/inbox`,
|
|
152
|
-
outbox: `https://${domain}/user/${username}/outbox`,
|
|
153
|
-
followers: `https://${domain}/user/${username}/followers`,
|
|
154
|
-
following: `https://${domain}/user/${username}/following`,
|
|
155
|
-
liked: `https://${domain}/user/${username}/liked`,
|
|
156
|
-
to: ['as:Public'],
|
|
157
|
-
publicKey: {
|
|
158
|
-
id: `https://${domain}/user/${username}/publickey`,
|
|
159
|
-
type: 'CryptographicKey',
|
|
160
|
-
owner: `https://${domain}/user/${username}`,
|
|
161
|
-
publicKeyPem: await getPublicKey(username, domain)
|
|
162
|
-
},
|
|
163
|
-
url: {
|
|
164
|
-
type: 'Link',
|
|
165
|
-
href: `https://${domain}/profile/${username}`,
|
|
166
|
-
mediaType: 'text/html'
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
// Just the types we use here
|
|
171
|
-
const isActivityType = (type) => ['Create', 'Update', 'Delete', 'Add', 'Remove', 'Follow', 'Accept', 'Reject', 'Like', 'Block', 'Flag', 'Undo'].includes(uppercase(type))
|
|
172
|
-
|
|
173
|
-
export async function makeObject (
|
|
174
|
-
username,
|
|
175
|
-
type,
|
|
176
|
-
num,
|
|
177
|
-
domain = defaultDomain,
|
|
178
|
-
extra = {}) {
|
|
179
|
-
const props = {
|
|
180
|
-
'@context': [
|
|
181
|
-
'https://www.w3.org/ns/activitystreams',
|
|
182
|
-
'https://purl.archive.org/socialweb/thread/1.0',
|
|
183
|
-
{ ostatus: 'http://ostatus.org/schema/1.0/' }
|
|
184
|
-
],
|
|
185
|
-
id: nockFormat({ username, type, num, domain }),
|
|
186
|
-
type: uppercase(type),
|
|
187
|
-
to: 'as:Public',
|
|
188
|
-
...extra
|
|
189
|
-
}
|
|
190
|
-
if (isActivityType(type)) {
|
|
191
|
-
props.actor = nockFormat({ username, domain })
|
|
192
|
-
} else {
|
|
193
|
-
props.attributedTo = nockFormat({ username, domain })
|
|
194
|
-
props.replies = nockFormat({ username, type, num, domain, obj: 'replies' })
|
|
195
|
-
props.shares = nockFormat({ username, type, num, domain, obj: 'shares' })
|
|
196
|
-
props.likes = nockFormat({ username, type, num, domain, obj: 'likes' })
|
|
197
|
-
props.thread = nockFormat({ username, type, num, domain, obj: 'thread' })
|
|
198
|
-
props.context = nockFormat({ username, type, num, domain, obj: 'context' })
|
|
199
|
-
props['ostatus:conversation'] = nockFormat({ username, type, num, domain, obj: 'conversation' })
|
|
200
|
-
}
|
|
201
|
-
return as2.import(props)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export const makeTransitive = (username, type, num, obj, domain = defaultDomain) =>
|
|
205
|
-
as2.import({
|
|
206
|
-
id: nockFormat({ username, type, num, obj, domain }),
|
|
207
|
-
type: uppercase(type),
|
|
208
|
-
to: 'as:Public',
|
|
209
|
-
actor: nockFormat({ username, domain }),
|
|
210
|
-
object: `https://${obj}`
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
const uppercase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
|
|
214
|
-
const lowercase = (str) => str.toLowerCase()
|
|
215
|
-
|
|
216
|
-
export const postInbox = {}
|
|
217
|
-
|
|
218
|
-
export const resetInbox = () => {
|
|
219
|
-
for (const username in postInbox) {
|
|
220
|
-
postInbox[username] = 0
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
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) =>
|
|
241
|
-
nock(`https://${domain}`)
|
|
242
|
-
.persist()
|
|
243
|
-
.get(/^\/.well-known\/webfinger/)
|
|
244
|
-
.reply(async function (uri, requestBody) {
|
|
245
|
-
captureRequestHeaders(domain, uri, this?.req)
|
|
246
|
-
const parsed = new URL(uri, `https://${domain}`)
|
|
247
|
-
const resource = parsed.searchParams.get('resource')
|
|
248
|
-
if (!resource) {
|
|
249
|
-
return [400, 'Bad Request']
|
|
250
|
-
}
|
|
251
|
-
const username = resource.slice(5).split('@')[0]
|
|
252
|
-
const webfinger = {
|
|
253
|
-
subject: resource,
|
|
254
|
-
links: [
|
|
255
|
-
{
|
|
256
|
-
rel: 'self',
|
|
257
|
-
type: 'application/activity+json',
|
|
258
|
-
href: `https://${domain}/user/${username}`
|
|
259
|
-
}
|
|
260
|
-
]
|
|
261
|
-
}
|
|
262
|
-
return [200,
|
|
263
|
-
JSON.stringify(webfinger),
|
|
264
|
-
{ 'Content-Type': 'application/jrd+json' }]
|
|
265
|
-
})
|
|
266
|
-
.get(/^\/user\/(\w+)$/)
|
|
267
|
-
.reply(async function (uri, requestBody) {
|
|
268
|
-
captureRequestHeaders(domain, uri, this?.req)
|
|
269
|
-
const username = uri.match(/^\/user\/(\w+)$/)[1]
|
|
270
|
-
const actor = await makeActor(username, domain)
|
|
271
|
-
const actorText = await actor.write(
|
|
272
|
-
{ additional_context: 'https://w3id.org/security/v1' }
|
|
273
|
-
)
|
|
274
|
-
return [200, actorText, { 'Content-Type': 'application/activity+json' }]
|
|
275
|
-
})
|
|
276
|
-
.post(/^\/user\/(\w+)\/inbox$/)
|
|
277
|
-
.reply(async function (uri, requestBody) {
|
|
278
|
-
captureRequestHeaders(domain, uri, this?.req)
|
|
279
|
-
const username = uri.match(/^\/user\/(\w+)\/inbox$/)[1]
|
|
280
|
-
if (username in postInbox) {
|
|
281
|
-
postInbox[username] += 1
|
|
282
|
-
} else {
|
|
283
|
-
postInbox[username] = 1
|
|
284
|
-
}
|
|
285
|
-
return [202, 'accepted']
|
|
286
|
-
})
|
|
287
|
-
.get(/^\/user\/(\w+)\/inbox$/)
|
|
288
|
-
.reply(async function (uri, requestBody) {
|
|
289
|
-
return [403, 'forbidden']
|
|
290
|
-
})
|
|
291
|
-
.get(/^\/user\/(\w+)\/publickey$/)
|
|
292
|
-
.reply(async function (uri, requestBody) {
|
|
293
|
-
captureRequestHeaders(domain, uri, this?.req)
|
|
294
|
-
const username = uri.match(/^\/user\/(\w+)\/publickey$/)[1]
|
|
295
|
-
const publicKey = await as2.import({
|
|
296
|
-
'@context': [
|
|
297
|
-
'https://www.w3.org/ns/activitystreams',
|
|
298
|
-
'https://w3id.org/security/v1'
|
|
299
|
-
],
|
|
300
|
-
id: `https://${domain}/user/${username}/publickey`,
|
|
301
|
-
owner: `https://${domain}/user/${username}`,
|
|
302
|
-
type: 'CryptographicKey',
|
|
303
|
-
publicKeyPem: await getPublicKey(username, domain)
|
|
304
|
-
})
|
|
305
|
-
const publicKeyText = await publicKey.write(
|
|
306
|
-
{ additional_context: 'https://w3id.org/security/v1' }
|
|
307
|
-
)
|
|
308
|
-
return [200, publicKeyText, { 'Content-Type': 'application/activity+json' }]
|
|
309
|
-
})
|
|
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
|
-
})
|
|
454
|
-
.get(/^\/user\/(\w+)\/(\w+)\/(\d+)$/)
|
|
455
|
-
.reply(async function (uri, requestBody) {
|
|
456
|
-
captureRequestHeaders(domain, uri, this?.req)
|
|
457
|
-
const match = uri.match(/^\/user\/(\w+)\/(\w+)\/(\d+)$/)
|
|
458
|
-
const username = match[1]
|
|
459
|
-
const type = uppercase(match[2])
|
|
460
|
-
const num = match[3]
|
|
461
|
-
const obj = await makeObject(username, type, num, domain)
|
|
462
|
-
const objText = await obj.write({ useOriginalContext: true })
|
|
463
|
-
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
464
|
-
})
|
|
465
|
-
.get(/^\/user\/(\w+)\/(\w+)\/(\d+)\/(.*)$/)
|
|
466
|
-
.reply(async function (uri, requestBody) {
|
|
467
|
-
captureRequestHeaders(domain, uri, this?.req)
|
|
468
|
-
const match = uri.match(/^\/user\/(\w+)\/(\w+)\/(\d+)\/(.*)$/)
|
|
469
|
-
const username = match[1]
|
|
470
|
-
const type = match[2]
|
|
471
|
-
const num = match[3]
|
|
472
|
-
const obj = match[4]
|
|
473
|
-
const act = await makeTransitive(username, type, num, obj, domain)
|
|
474
|
-
const actText = await act.write()
|
|
475
|
-
return [200, actText, { 'Content-Type': 'application/activity+json' }]
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
export function nockFormat ({ username, type, num, obj, key, collection, page, domain = defaultDomain }) {
|
|
479
|
-
let url = `https://${domain}/user/${username}`
|
|
480
|
-
if (key) {
|
|
481
|
-
url = `${url}/publickey`
|
|
482
|
-
} else if (collection) {
|
|
483
|
-
url = `${url}/${collection}`
|
|
484
|
-
} else {
|
|
485
|
-
if (type && num) {
|
|
486
|
-
url = `${url}/${lowercase(type)}/${num}`
|
|
487
|
-
if (obj) {
|
|
488
|
-
if (obj.startsWith('https://')) {
|
|
489
|
-
url = `${url}/${obj.slice(8)}`
|
|
490
|
-
} else {
|
|
491
|
-
url = `${url}/${obj}`
|
|
492
|
-
}
|
|
493
|
-
} else if (typeof page === 'number') {
|
|
494
|
-
url = `${url}/page/${page}`
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return url
|
|
499
|
-
}
|