@chrisburnell/eleventy-cache-webmentions 2.1.11 → 2.2.1

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