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