@chrisburnell/eleventy-cache-webmentions 2.3.2 → 2.3.3
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/README.md +25 -4
- package/eleventy-cache-webmentions.cjs +4 -688
- package/eleventy-cache-webmentions.js +40 -675
- package/package.json +8 -6
- package/src/core.cjs +689 -0
- package/.cache/eleventy-fetch-68a403136c61abd104ade94e69ba62 +0 -1
- package/.cache/eleventy-fetch-68a403136c61abd104ade94e69ba62.json +0 -1
- package/eleventy-cache-webmentions.min.cjs +0 -1
- package/eleventy-cache-webmentions.min.js +0 -1
- package/eslint.config.js +0 -42
|
@@ -1,680 +1,45 @@
|
|
|
1
1
|
import { AssetCache } from "@11ty/eleventy-fetch";
|
|
2
2
|
import { styleText } from "node:util";
|
|
3
3
|
import sanitizeHTML from "sanitize-html";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @property {string} key
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @typedef {OptionsDefaults & OptionsUserInput} Options
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @typedef {object} Webmention
|
|
53
|
-
* @property {string} [source]
|
|
54
|
-
* @property {string} [url]
|
|
55
|
-
* @property {string} [target]
|
|
56
|
-
* @property {string} [published]
|
|
57
|
-
* @property {string} [contentSanitized]
|
|
58
|
-
* @property {object} [content]
|
|
59
|
-
* @property {string} [content.html]
|
|
60
|
-
* @property {string} [content.value]
|
|
61
|
-
* @property {object} [data]
|
|
62
|
-
* @property {string} [data.title]
|
|
63
|
-
* @property {string} [data.url]
|
|
64
|
-
* @property {string} [data.published]
|
|
65
|
-
* @property {string} [data.content]
|
|
66
|
-
* @property {string} [type]
|
|
67
|
-
* @property {object} [activity]
|
|
68
|
-
* @property {string} [activity.type]
|
|
69
|
-
* @property {boolean} [verified]
|
|
70
|
-
* @property {string} ["wm-property"]
|
|
71
|
-
* @property {string} ["wm-received"]
|
|
72
|
-
* @property {string} ["wm-source"]
|
|
73
|
-
* @property {string} ["wm-target"]
|
|
74
|
-
* @property {string} ["verified_date"]
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @typedef {"bookmark-of"|"like-of"|"repost-of"|"mention-of"|"in-reply-to"} WebmentionType
|
|
79
|
-
*/
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @param {string} url
|
|
83
|
-
* @param {string} domain
|
|
84
|
-
* @returns {string}
|
|
85
|
-
*/
|
|
86
|
-
const absoluteURL = (url, domain) => {
|
|
87
|
-
try {
|
|
88
|
-
return new URL(url, domain).toString();
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error(
|
|
91
|
-
`Trying to convert ${styleText(
|
|
92
|
-
"bold",
|
|
93
|
-
url,
|
|
94
|
-
)} to be an absolute url with base ${styleText(
|
|
95
|
-
"bold",
|
|
96
|
-
domain,
|
|
97
|
-
)} and failed.`,
|
|
98
|
-
error,
|
|
99
|
-
);
|
|
100
|
-
return url;
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* @param {string} url
|
|
106
|
-
* @returns {string}
|
|
107
|
-
*/
|
|
108
|
-
const baseURL = (url) => {
|
|
109
|
-
let hashSplit = url.split("#");
|
|
110
|
-
let queryparamSplit = hashSplit[0].split("?");
|
|
111
|
-
return queryparamSplit[0];
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @param {string} url
|
|
116
|
-
* @param {{[key: string]: string}} [urlReplacements]
|
|
117
|
-
* @returns {string}
|
|
118
|
-
*/
|
|
119
|
-
const fixURL = (url, urlReplacements) => {
|
|
120
|
-
return Object.entries(urlReplacements).reduce(
|
|
121
|
-
(accumulator, [key, value]) => {
|
|
122
|
-
const regex = new RegExp(key, "g");
|
|
123
|
-
return accumulator.replace(regex, value);
|
|
124
|
-
},
|
|
125
|
-
url,
|
|
126
|
-
);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* @param {string} url
|
|
131
|
-
* @returns {string}
|
|
132
|
-
*/
|
|
133
|
-
const hostname = (url) => {
|
|
134
|
-
if (typeof url === "string" && url.includes("//")) {
|
|
135
|
-
const urlObject = new URL(url);
|
|
136
|
-
return urlObject.hostname;
|
|
137
|
-
}
|
|
138
|
-
return url;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* @param {string|number|Date} date
|
|
143
|
-
* @returns {number}
|
|
144
|
-
*/
|
|
145
|
-
const epoch = (date) => {
|
|
146
|
-
return new Date(date).getTime();
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* @param {Array<Webmention>} webmentions
|
|
151
|
-
* @returns {Array<Webmention>}
|
|
152
|
-
*/
|
|
153
|
-
const removeDuplicates = (webmentions) => {
|
|
154
|
-
return [
|
|
155
|
-
...webmentions
|
|
156
|
-
.reduce((map, webmention) => {
|
|
157
|
-
const key =
|
|
158
|
-
webmention === null || webmention === undefined
|
|
159
|
-
? webmention
|
|
160
|
-
: getUniqueKey(webmention);
|
|
161
|
-
if (!map.has(key)) {
|
|
162
|
-
map.set(key, webmention);
|
|
163
|
-
}
|
|
164
|
-
return map;
|
|
165
|
-
}, new Map())
|
|
166
|
-
.values(),
|
|
167
|
-
];
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* @param {Webmention} webmention
|
|
172
|
-
* @returns {string|undefined}
|
|
173
|
-
*/
|
|
174
|
-
export const getPublished = (webmention) => {
|
|
175
|
-
return (
|
|
176
|
-
webmention?.["data"]?.["published"] ||
|
|
177
|
-
webmention["published"] ||
|
|
178
|
-
webmention["wm-received"] ||
|
|
179
|
-
webmention["verified_date"]
|
|
180
|
-
);
|
|
181
|
-
};
|
|
182
|
-
export const getWebmentionPublished = getPublished;
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* @param {Webmention} webmention
|
|
186
|
-
* @returns {string|undefined}
|
|
187
|
-
*/
|
|
188
|
-
export const getReceived = (webmention) => {
|
|
189
|
-
return (
|
|
190
|
-
webmention["wm-received"] ||
|
|
191
|
-
webmention["verified_date"] ||
|
|
192
|
-
webmention["published"] ||
|
|
193
|
-
webmention?.["data"]?.["published"]
|
|
194
|
-
);
|
|
195
|
-
};
|
|
196
|
-
export const getWebmentionReceived = getReceived;
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* @param {Webmention} webmention
|
|
200
|
-
* @returns {string}
|
|
201
|
-
*/
|
|
202
|
-
export const getContent = (webmention) => {
|
|
203
|
-
return (
|
|
204
|
-
webmention?.["contentSanitized"] ||
|
|
205
|
-
webmention?.["content"]?.["html"] ||
|
|
206
|
-
webmention?.["content"]?.["value"] ||
|
|
207
|
-
webmention?.["content"] ||
|
|
208
|
-
webmention?.["data"]?.["content"] ||
|
|
209
|
-
""
|
|
210
|
-
);
|
|
211
|
-
};
|
|
212
|
-
export const getWebmentionContent = getContent;
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* @param {Webmention} webmention
|
|
216
|
-
* @returns {string|undefined}
|
|
217
|
-
*/
|
|
218
|
-
export const getSource = (webmention) => {
|
|
219
|
-
return (
|
|
220
|
-
webmention["wm-source"] ||
|
|
221
|
-
webmention["source"] ||
|
|
222
|
-
webmention?.["data"]?.["url"] ||
|
|
223
|
-
webmention["url"]
|
|
224
|
-
);
|
|
225
|
-
};
|
|
226
|
-
export const getWebmentionSource = getSource;
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* @param {Webmention} webmention
|
|
230
|
-
* @returns {string|undefined}
|
|
231
|
-
*/
|
|
232
|
-
export const getURL = (webmention) => {
|
|
233
|
-
return (
|
|
234
|
-
webmention?.["data"]?.["url"] ||
|
|
235
|
-
webmention["url"] ||
|
|
236
|
-
webmention["wm-source"] ||
|
|
237
|
-
webmention["source"]
|
|
238
|
-
);
|
|
239
|
-
};
|
|
240
|
-
export const getWebmentionURL = getURL;
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* @param {Webmention} webmention
|
|
244
|
-
* @returns {string|undefined}
|
|
245
|
-
*/
|
|
246
|
-
export const getTarget = (webmention) => {
|
|
247
|
-
return webmention["wm-target"] || webmention["target"];
|
|
248
|
-
};
|
|
249
|
-
export const getWebmentionTarget = getTarget;
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* @param {Webmention} webmention
|
|
253
|
-
* @returns {string}
|
|
254
|
-
*/
|
|
255
|
-
export const getUniqueKey = (webmention) => {
|
|
256
|
-
return JSON.stringify([getSource(webmention), getTarget(webmention)]);
|
|
257
|
-
};
|
|
258
|
-
export const getWebmentionUniqueKey = getUniqueKey;
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* @param {Webmention} webmention
|
|
262
|
-
* @returns {string|undefined}
|
|
263
|
-
*/
|
|
264
|
-
export const getType = (webmention) => {
|
|
265
|
-
return (
|
|
266
|
-
webmention["wm-property"] ||
|
|
267
|
-
webmention?.["activity"]?.["type"] ||
|
|
268
|
-
webmention["type"]
|
|
269
|
-
);
|
|
270
|
-
};
|
|
271
|
-
export const getWebmentionType = getType;
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* @param {Array<Webmention>} webmentions
|
|
275
|
-
* @param {WebmentionType|Array<WebmentionType>} types
|
|
276
|
-
* @returns {Array<Webmention>}
|
|
277
|
-
*/
|
|
278
|
-
export const getByTypes = (webmentions, types) => {
|
|
279
|
-
return webmentions.filter((webmention) => {
|
|
280
|
-
if (typeof types === "string") {
|
|
281
|
-
return types === getType(webmention);
|
|
282
|
-
}
|
|
283
|
-
return types.includes(getType(webmention));
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
export const getByType = getByTypes;
|
|
287
|
-
export const getWebmentionsByTypes = getByTypes;
|
|
288
|
-
export const getWebmentionsByType = getByTypes;
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* @param {Array<Webmention>} webmentions
|
|
292
|
-
* @param {Array<string>} blocklist
|
|
293
|
-
* @returns {Array<Webmention>}
|
|
294
|
-
*/
|
|
295
|
-
export const processBlocklist = (webmentions, blocklist) => {
|
|
296
|
-
return webmentions.filter((webmention) => {
|
|
297
|
-
let url = getSource(webmention);
|
|
298
|
-
let source = getSource(webmention);
|
|
299
|
-
for (let blocklistURL of blocklist) {
|
|
300
|
-
if (
|
|
301
|
-
url.includes(blocklistURL.replace(/\/?$/, "/")) ||
|
|
302
|
-
source.includes(blocklistURL.replace(/\/?$/, "/"))
|
|
303
|
-
) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return true;
|
|
308
|
-
});
|
|
309
|
-
};
|
|
310
|
-
export const processWebmentionBlocklist = processBlocklist;
|
|
311
|
-
export const processWebmentionsBlocklist = processBlocklist;
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* @param {Array<Webmention>} webmentions
|
|
315
|
-
* @param {Array<string>} allowlist
|
|
316
|
-
* @returns {Array<Webmention>}
|
|
317
|
-
*/
|
|
318
|
-
export const processAllowlist = (webmentions, allowlist) => {
|
|
319
|
-
return webmentions.filter((webmention) => {
|
|
320
|
-
let url = getSource(webmention);
|
|
321
|
-
let source = getSource(webmention);
|
|
322
|
-
for (let allowlistURL of allowlist) {
|
|
323
|
-
if (
|
|
324
|
-
url.includes(allowlistURL.replace(/\/?$/, "/")) ||
|
|
325
|
-
source.includes(allowlistURL.replace(/\/?$/, "/"))
|
|
326
|
-
) {
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return false;
|
|
331
|
-
});
|
|
332
|
-
};
|
|
333
|
-
export const processWebmentionAllowlist = processAllowlist;
|
|
334
|
-
export const processWebmentionsAllowlist = processAllowlist;
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* @param {Options} options
|
|
338
|
-
* @param {Array<Webmention>} webmentions
|
|
339
|
-
* @param {string} url
|
|
340
|
-
* @returns {Promise<{found: number, webmentions: Array<Webmention>}>}
|
|
341
|
-
*/
|
|
342
|
-
export const fetchWebmentions = async (options, webmentions, url) => {
|
|
343
|
-
return await fetch(url)
|
|
344
|
-
.then(async (response) => {
|
|
345
|
-
if (!response.ok) {
|
|
346
|
-
return Promise.reject(response);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const feed = await response.json();
|
|
350
|
-
|
|
351
|
-
if (!(options.key in feed)) {
|
|
352
|
-
console.log(
|
|
353
|
-
`${styleText("gray", `[${hostname(options.domain)}]`)} ${
|
|
354
|
-
options.key
|
|
355
|
-
} was not found as a key in the response from ${styleText(
|
|
356
|
-
"bold",
|
|
357
|
-
hostname(options.feed),
|
|
358
|
-
)}!`,
|
|
359
|
-
);
|
|
360
|
-
return Promise.reject(response);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Fetched Webmentions replace cached ones with the same
|
|
364
|
-
// source / target pair
|
|
365
|
-
const fetchedKeys = new Set(
|
|
366
|
-
feed[options.key].map(getUniqueKey),
|
|
367
|
-
);
|
|
368
|
-
webmentions = [
|
|
369
|
-
...feed[options.key],
|
|
370
|
-
...webmentions.filter(
|
|
371
|
-
(wm) => !fetchedKeys.has(getUniqueKey(wm)),
|
|
372
|
-
),
|
|
373
|
-
];
|
|
374
|
-
// Remove any remaining duplicates by source / target pair
|
|
375
|
-
webmentions = removeDuplicates(webmentions);
|
|
376
|
-
// Process the blocklist, if it has any entries
|
|
377
|
-
if (options.blocklist.length) {
|
|
378
|
-
webmentions = processBlocklist(webmentions, options.blocklist);
|
|
379
|
-
}
|
|
380
|
-
// Process the allowlist, if it has any entries
|
|
381
|
-
if (options.allowlist.length) {
|
|
382
|
-
webmentions = processAllowlist(webmentions, options.allowlist);
|
|
383
|
-
}
|
|
384
|
-
// Sort webmentions by received date for getting most recent Webmention on subsequent requests
|
|
385
|
-
webmentions = webmentions.sort((a, b) => {
|
|
386
|
-
return epoch(getReceived(b)) - epoch(getReceived(a));
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
found: feed[options.key].length,
|
|
391
|
-
webmentions: webmentions,
|
|
392
|
-
};
|
|
393
|
-
})
|
|
394
|
-
.catch((error) => {
|
|
395
|
-
console.warn(
|
|
396
|
-
`${styleText(
|
|
397
|
-
"gray",
|
|
398
|
-
`[${hostname(options.domain)}]`,
|
|
399
|
-
)} Something went wrong with your Webmention request to ${styleText(
|
|
400
|
-
"bold",
|
|
401
|
-
hostname(options.feed),
|
|
402
|
-
)}!`,
|
|
403
|
-
);
|
|
404
|
-
console.warn(error instanceof Error ? error.message : error);
|
|
405
|
-
|
|
406
|
-
return {
|
|
407
|
-
found: 0,
|
|
408
|
-
webmentions: webmentions,
|
|
409
|
-
};
|
|
410
|
-
});
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* @param {Options} options
|
|
415
|
-
* @returns {Promise<Array<Webmention>>}
|
|
416
|
-
*/
|
|
417
|
-
export const retrieveWebmentions = async (options) => {
|
|
418
|
-
if (!options.domain) {
|
|
419
|
-
throw new Error(
|
|
420
|
-
"`domain` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.",
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (!options.feed) {
|
|
425
|
-
throw new Error(
|
|
426
|
-
"`feed` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.",
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (!options.key) {
|
|
431
|
-
throw new Error(
|
|
432
|
-
"`key` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.",
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
let asset = new AssetCache(
|
|
437
|
-
options.uniqueKey || `webmentions-${hostname(options.domain)}`,
|
|
438
|
-
options.cacheDirectory,
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
let webmentions = [];
|
|
442
|
-
|
|
443
|
-
// Unless specifically getting fresh Webmentions, if there is a cached file
|
|
444
|
-
// at all, grab its contents now
|
|
445
|
-
if (asset.isCacheValid("9001y") && !options.refresh) {
|
|
446
|
-
webmentions = await asset.getCachedValue();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Get the number of cached Webmentions for diffing against fetched
|
|
450
|
-
// Webmentions later
|
|
451
|
-
const webmentionsCachedLength = webmentions.length;
|
|
452
|
-
|
|
453
|
-
// If there is a cached file but it is outside of expiry, fetch fresh
|
|
454
|
-
// results since the most recent Webmention
|
|
455
|
-
if (!asset.isCacheValid(options.refresh ? "0s" : options.duration)) {
|
|
456
|
-
const performanceStart = process.hrtime();
|
|
457
|
-
// Get the received date of the most recent Webmention, if it exists
|
|
458
|
-
const since = webmentions.length ? getReceived(webmentions[0]) : false;
|
|
459
|
-
// Build the URL for the fetch request
|
|
460
|
-
const url = `${options.feed}${
|
|
461
|
-
since
|
|
462
|
-
? `${options.feed.includes("?") ? "&" : "?"}since=${since}`
|
|
463
|
-
: ""
|
|
464
|
-
}`;
|
|
465
|
-
|
|
466
|
-
// If using webmention.io, loop through pages until no results found
|
|
467
|
-
if (url.includes("https://webmention.io")) {
|
|
468
|
-
const urlObject = new URL(url);
|
|
469
|
-
const perPage =
|
|
470
|
-
Number(urlObject.searchParams.get("per-page")) || 1000;
|
|
471
|
-
urlObject.searchParams.delete("per-page");
|
|
472
|
-
// Start on page 0, to increment per subsequent request
|
|
473
|
-
let page = 0;
|
|
474
|
-
// Loop until a break condition is hit
|
|
475
|
-
while (true) {
|
|
476
|
-
const urlPaginated =
|
|
477
|
-
urlObject.href + `&per-page=${perPage}&page=${page}`;
|
|
478
|
-
const fetched = await fetchWebmentions(
|
|
479
|
-
options,
|
|
480
|
-
webmentions,
|
|
481
|
-
urlPaginated,
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
// An error occurred during fetching paged results → break
|
|
485
|
-
if (!fetched && !fetched.found && !fetched.webmentions) {
|
|
486
|
-
break;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Page has no Webmentions → break
|
|
490
|
-
if (fetched.found === 0) {
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
webmentions = fetched.webmentions;
|
|
495
|
-
|
|
496
|
-
// If there are less Webmentions found than should be in each
|
|
497
|
-
// page → break
|
|
498
|
-
if (fetched.found < perPage) {
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Increment page
|
|
503
|
-
page += 1;
|
|
504
|
-
// Throttle next request
|
|
505
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
506
|
-
}
|
|
507
|
-
} else {
|
|
508
|
-
const fetched = await fetchWebmentions(options, webmentions, url);
|
|
509
|
-
webmentions = fetched.webmentions;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Process the blocklist, if it has any entries
|
|
513
|
-
if (options.blocklist.length) {
|
|
514
|
-
webmentions = processBlocklist(webmentions, options.blocklist);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Process the allowlist, if it has any entries
|
|
518
|
-
if (options.allowlist.length) {
|
|
519
|
-
webmentions = processAllowlist(webmentions, options.allowlist);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
await asset.save(webmentions, "json");
|
|
523
|
-
|
|
524
|
-
const performance = process.hrtime(performanceStart);
|
|
525
|
-
|
|
526
|
-
// Add a console message with the number of fetched and processed Webmentions, if any
|
|
527
|
-
if (webmentionsCachedLength < webmentions.length) {
|
|
528
|
-
console.log(
|
|
529
|
-
`${styleText(
|
|
530
|
-
"gray",
|
|
531
|
-
`[${hostname(options.domain)}]`,
|
|
532
|
-
)} ${styleText(
|
|
533
|
-
"bold",
|
|
534
|
-
String(webmentions.length - webmentionsCachedLength),
|
|
535
|
-
)} new Webmentions fetched into cache in ${styleText(
|
|
536
|
-
"bold",
|
|
537
|
-
(performance[0] + performance[1] / 1e9).toFixed(3) +
|
|
538
|
-
" seconds",
|
|
539
|
-
)}.`,
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return webmentions;
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
/** @type {Array<Webmention>} */
|
|
548
|
-
const WEBMENTIONS = {};
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* @param {Options} options
|
|
552
|
-
* @returns {Promise<{[key: string]: Array<Webmention>}>}
|
|
553
|
-
*/
|
|
554
|
-
export const webmentionsByURL = async (options) => {
|
|
555
|
-
if (Object.keys(WEBMENTIONS).length) {
|
|
556
|
-
return WEBMENTIONS;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
let rawWebmentions = await retrieveWebmentions(options);
|
|
560
|
-
|
|
561
|
-
// Fix local URLs based on urlReplacements and sort Webmentions into groups
|
|
562
|
-
// by target base URL
|
|
563
|
-
rawWebmentions.forEach((webmention) => {
|
|
564
|
-
let url = baseURL(
|
|
565
|
-
fixURL(
|
|
566
|
-
getTarget(webmention).replace(/\/?$/, "/"),
|
|
567
|
-
options.urlReplacements,
|
|
568
|
-
),
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
if (!WEBMENTIONS[url]) {
|
|
572
|
-
WEBMENTIONS[url] = [];
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
WEBMENTIONS[url].push(webmention);
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
return WEBMENTIONS;
|
|
579
|
-
};
|
|
580
|
-
export const webmentionsByUrl = webmentionsByURL;
|
|
581
|
-
export const filteredWebmentions = webmentionsByURL;
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* @param {Options} options
|
|
585
|
-
* @param {string} url
|
|
586
|
-
* @param {WebmentionType|Array<WebmentionType>} [types]
|
|
587
|
-
* @returns {Promise<Array<Webmention>>}
|
|
588
|
-
*/
|
|
589
|
-
export const getWebmentions = async (options, url, types = []) => {
|
|
590
|
-
const webmentions = await webmentionsByURL(options);
|
|
591
|
-
url = absoluteURL(url, options.domain);
|
|
592
|
-
|
|
593
|
-
if (!url || !webmentions || !webmentions[url]) {
|
|
594
|
-
return [];
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
return (
|
|
598
|
-
webmentions[url]
|
|
599
|
-
// Filter webmentions by allowed response post types
|
|
600
|
-
.filter((entry) => {
|
|
601
|
-
return typeof types === "object" && Object.keys(types).length
|
|
602
|
-
? types.includes(getType(entry))
|
|
603
|
-
: typeof types === "string"
|
|
604
|
-
? types === getType(entry)
|
|
605
|
-
: true;
|
|
606
|
-
})
|
|
607
|
-
// Sanitize content of webmentions against HTML limit
|
|
608
|
-
.map((entry) => {
|
|
609
|
-
const html = getContent(entry);
|
|
610
|
-
|
|
611
|
-
if (html.length) {
|
|
612
|
-
entry.contentSanitized = sanitizeHTML(
|
|
613
|
-
html,
|
|
614
|
-
options.allowedHTML,
|
|
615
|
-
);
|
|
616
|
-
if (html.length > options.maximumHtmlLength) {
|
|
617
|
-
entry.contentSanitized = `${
|
|
618
|
-
options.maximumHtmlText
|
|
619
|
-
} <a href="${getSource(entry)}">${getSource(
|
|
620
|
-
entry,
|
|
621
|
-
)}</a>`;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
return entry;
|
|
626
|
-
})
|
|
627
|
-
// Sort by published
|
|
628
|
-
.sort((a, b) => {
|
|
629
|
-
return epoch(getPublished(a)) - epoch(getPublished(b));
|
|
630
|
-
})
|
|
631
|
-
);
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* @param {object} eleventyConfig
|
|
636
|
-
* @param {Options} [options]
|
|
637
|
-
*/
|
|
638
|
-
export const eleventyCacheWebmentions = async (
|
|
639
|
-
eleventyConfig,
|
|
640
|
-
options = {},
|
|
641
|
-
) => {
|
|
642
|
-
options = Object.assign(defaults, options);
|
|
643
|
-
|
|
644
|
-
const byURL = await webmentionsByURL(options);
|
|
645
|
-
const all = Object.values(byURL).reduce(
|
|
646
|
-
(array, webmentions) => [...array, ...webmentions],
|
|
647
|
-
[],
|
|
648
|
-
);
|
|
649
|
-
|
|
650
|
-
// Global Data
|
|
651
|
-
eleventyConfig.addGlobalData("webmentionsDefaults", defaults);
|
|
652
|
-
eleventyConfig.addGlobalData("webmentionsOptions", options);
|
|
653
|
-
eleventyConfig.addGlobalData("webmentionsByURL", byURL);
|
|
654
|
-
eleventyConfig.addGlobalData("webmentionsByUrl", byURL);
|
|
655
|
-
eleventyConfig.addGlobalData("webmentionsAll", all);
|
|
656
|
-
|
|
657
|
-
// Liquid Filters
|
|
658
|
-
eleventyConfig.addLiquidFilter("getWebmentionsByType", getByTypes);
|
|
659
|
-
eleventyConfig.addLiquidFilter("getWebmentionsByTypes", getByTypes);
|
|
660
|
-
eleventyConfig.addLiquidFilter("getWebmentionPublished", getPublished);
|
|
661
|
-
eleventyConfig.addLiquidFilter("getWebmentionReceived", getReceived);
|
|
662
|
-
eleventyConfig.addLiquidFilter("getWebmentionContent", getContent);
|
|
663
|
-
eleventyConfig.addLiquidFilter("getWebmentionSource", getSource);
|
|
664
|
-
eleventyConfig.addLiquidFilter("getWebmentionURL", getURL);
|
|
665
|
-
eleventyConfig.addLiquidFilter("getWebmentionTarget", getTarget);
|
|
666
|
-
eleventyConfig.addLiquidFilter("getWebmentionType", getType);
|
|
667
|
-
|
|
668
|
-
// Nunjucks Filters
|
|
669
|
-
eleventyConfig.addNunjucksFilter("getWebmentionsByType", getByTypes);
|
|
670
|
-
eleventyConfig.addNunjucksFilter("getWebmentionsByTypes", getByTypes);
|
|
671
|
-
eleventyConfig.addNunjucksFilter("getWebmentionPublished", getPublished);
|
|
672
|
-
eleventyConfig.addNunjucksFilter("getWebmentionReceived", getReceived);
|
|
673
|
-
eleventyConfig.addNunjucksFilter("getWebmentionContent", getContent);
|
|
674
|
-
eleventyConfig.addNunjucksFilter("getWebmentionSource", getSource);
|
|
675
|
-
eleventyConfig.addNunjucksFilter("getWebmentionURL", getURL);
|
|
676
|
-
eleventyConfig.addNunjucksFilter("getWebmentionTarget", getTarget);
|
|
677
|
-
eleventyConfig.addNunjucksFilter("getWebmentionType", getType);
|
|
678
|
-
};
|
|
4
|
+
import createCore from "./src/core.cjs";
|
|
5
|
+
|
|
6
|
+
const core = createCore({ AssetCache, styleText, sanitizeHTML });
|
|
7
|
+
|
|
8
|
+
export const {
|
|
9
|
+
defaults,
|
|
10
|
+
getPublished,
|
|
11
|
+
getWebmentionPublished,
|
|
12
|
+
getReceived,
|
|
13
|
+
getWebmentionReceived,
|
|
14
|
+
getContent,
|
|
15
|
+
getWebmentionContent,
|
|
16
|
+
getSource,
|
|
17
|
+
getWebmentionSource,
|
|
18
|
+
getURL,
|
|
19
|
+
getWebmentionURL,
|
|
20
|
+
getTarget,
|
|
21
|
+
getWebmentionTarget,
|
|
22
|
+
getUniqueKey,
|
|
23
|
+
getWebmentionUniqueKey,
|
|
24
|
+
getType,
|
|
25
|
+
getWebmentionType,
|
|
26
|
+
getByTypes,
|
|
27
|
+
getByType,
|
|
28
|
+
getWebmentionsByTypes,
|
|
29
|
+
getWebmentionsByType,
|
|
30
|
+
processBlocklist,
|
|
31
|
+
processWebmentionBlocklist,
|
|
32
|
+
processWebmentionsBlocklist,
|
|
33
|
+
processAllowlist,
|
|
34
|
+
processWebmentionAllowlist,
|
|
35
|
+
processWebmentionsAllowlist,
|
|
36
|
+
fetchWebmentions,
|
|
37
|
+
retrieveWebmentions,
|
|
38
|
+
webmentionsByURL,
|
|
39
|
+
webmentionsByUrl,
|
|
40
|
+
filteredWebmentions,
|
|
41
|
+
getWebmentions,
|
|
42
|
+
eleventyCacheWebmentions,
|
|
43
|
+
} = core;
|
|
679
44
|
|
|
680
45
|
export default eleventyCacheWebmentions;
|