@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,13 +1,76 @@
1
- const sanitizeHTML = require("sanitize-html");
2
- const { AssetCache } = require("@11ty/eleventy-fetch");
3
- const { styleText } = require("node:util");
4
-
5
- const defaults = {
1
+ import { AssetCache } from "@11ty/eleventy-fetch";
2
+ import { styleText } from "node:util";
3
+ import sanitizeHTML from "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
+ export const defaults = {
6
68
  refresh: false,
7
69
  duration: "1d",
8
70
  uniqueKey: "webmentions",
71
+ cacheDirectory: undefined,
9
72
  allowedHTML: {
10
- allowedTags: ["b", "i", "em", "strong", "a"],
73
+ allowedTags: ["a", "b", "em", "i", "strong"],
11
74
  allowedAttributes: {
12
75
  a: ["href"],
13
76
  },
@@ -19,6 +82,11 @@ const defaults = {
19
82
  maximumHtmlText: "mentioned this in",
20
83
  };
21
84
 
85
+ /**
86
+ * @param {string} url
87
+ * @param {string} domain
88
+ * @returns {string}
89
+ */
22
90
  const absoluteURL = (url, domain) => {
23
91
  try {
24
92
  return new URL(url, domain).toString();
@@ -36,13 +104,22 @@ const absoluteURL = (url, domain) => {
36
104
  }
37
105
  };
38
106
 
39
- const baseUrl = (url) => {
107
+ /**
108
+ * @param {string} url
109
+ * @returns {string}
110
+ */
111
+ const baseURL = (url) => {
40
112
  let hashSplit = url.split("#");
41
113
  let queryparamSplit = hashSplit[0].split("?");
42
114
  return queryparamSplit[0];
43
115
  };
44
116
 
45
- const fixUrl = (url, urlReplacements) => {
117
+ /**
118
+ * @param {string} url
119
+ * @param {Object<string, string>} [urlReplacements]
120
+ * @returns {string}
121
+ */
122
+ const fixURL = (url, urlReplacements) => {
46
123
  return Object.entries(urlReplacements).reduce(
47
124
  (accumulator, [key, value]) => {
48
125
  const regex = new RegExp(key, "g");
@@ -52,19 +129,52 @@ const fixUrl = (url, urlReplacements) => {
52
129
  );
53
130
  };
54
131
 
55
- const hostname = (value) => {
56
- if (typeof value === "string" && value.includes("//")) {
57
- const urlObject = new URL(value);
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);
58
139
  return urlObject.hostname;
59
140
  }
60
- return value;
141
+ return url;
61
142
  };
62
143
 
63
- const epoch = (value) => {
64
- return new Date(value).getTime();
144
+ /**
145
+ * @param {string|number|Date} date
146
+ * @returns {number}
147
+ */
148
+ const epoch = (date) => {
149
+ return new Date(date).getTime();
65
150
  };
66
151
 
67
- const getPublished = (webmention) => {
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
+ export const getPublished = (webmention) => {
68
178
  return (
69
179
  webmention?.["data"]?.["published"] ||
70
180
  webmention["published"] ||
@@ -72,8 +182,13 @@ const getPublished = (webmention) => {
72
182
  webmention["verified_date"]
73
183
  );
74
184
  };
185
+ export const getWebmentionPublished = getPublished;
75
186
 
76
- const getReceived = (webmention) => {
187
+ /**
188
+ * @param {Webmention} webmention
189
+ * @returns {string|undefined}
190
+ */
191
+ export const getReceived = (webmention) => {
77
192
  return (
78
193
  webmention["wm-received"] ||
79
194
  webmention["verified_date"] ||
@@ -81,8 +196,13 @@ const getReceived = (webmention) => {
81
196
  webmention?.["data"]?.["published"]
82
197
  );
83
198
  };
199
+ export const getWebmentionReceived = getReceived;
84
200
 
85
- const getContent = (webmention) => {
201
+ /**
202
+ * @param {Webmention} webmention
203
+ * @returns {string}
204
+ */
205
+ export const getContent = (webmention) => {
86
206
  return (
87
207
  webmention?.["contentSanitized"] ||
88
208
  webmention?.["content"]?.["html"] ||
@@ -92,8 +212,13 @@ const getContent = (webmention) => {
92
212
  ""
93
213
  );
94
214
  };
215
+ export const getWebmentionContent = getContent;
95
216
 
96
- const getSource = (webmention) => {
217
+ /**
218
+ * @param {Webmention} webmention
219
+ * @returns {string|undefined}
220
+ */
221
+ export const getSource = (webmention) => {
97
222
  return (
98
223
  webmention["wm-source"] ||
99
224
  webmention["source"] ||
@@ -101,8 +226,13 @@ const getSource = (webmention) => {
101
226
  webmention["url"]
102
227
  );
103
228
  };
229
+ export const getWebmentionSource = getSource;
104
230
 
105
- const getURL = (webmention) => {
231
+ /**
232
+ * @param {Webmention} webmention
233
+ * @returns {string|undefined}
234
+ */
235
+ export const getURL = (webmention) => {
106
236
  return (
107
237
  webmention?.["data"]?.["url"] ||
108
238
  webmention["url"] ||
@@ -110,49 +240,53 @@ const getURL = (webmention) => {
110
240
  webmention["source"]
111
241
  );
112
242
  };
243
+ export const getWebmentionURL = getURL;
113
244
 
114
- const getTarget = (webmention) => {
245
+ /**
246
+ * @param {Webmention} webmention
247
+ * @returns {string|undefined}
248
+ */
249
+ export const getTarget = (webmention) => {
115
250
  return webmention["wm-target"] || webmention["target"];
116
251
  };
252
+ export const getWebmentionTarget = getTarget;
117
253
 
118
- const getType = (webmention) => {
254
+ /**
255
+ * @param {Webmention} webmention
256
+ * @returns {string|undefined}
257
+ */
258
+ export const getType = (webmention) => {
119
259
  return (
120
260
  webmention["wm-property"] ||
121
261
  webmention?.["activity"]?.["type"] ||
122
262
  webmention["type"]
123
263
  );
124
264
  };
125
-
126
- const getByType = (webmentions, allowedType) => {
127
- return webmentions.filter((webmention) => {
128
- return allowedType === getType(webmention);
129
- });
130
- };
131
-
132
- const getByTypes = (webmentions, allowedTypes) => {
265
+ export const getWebmentionType = getType;
266
+
267
+ /**
268
+ * @param {Webmention[]} webmentions
269
+ * @param {WebmentionType|WebmentionType[]} types
270
+ * @returns {Webmention[]}
271
+ */
272
+ export const getByTypes = (webmentions, types) => {
133
273
  return webmentions.filter((webmention) => {
134
- return allowedTypes.includes(getType(webmention));
274
+ if (typeof types === "string") {
275
+ return types === getType(webmention);
276
+ }
277
+ return types.includes(getType(webmention));
135
278
  });
136
279
  };
137
-
138
- const removeDuplicates = (webmentions) => {
139
- return [
140
- ...webmentions
141
- .reduce((map, webmention) => {
142
- const key =
143
- webmention === null || webmention === undefined
144
- ? webmention
145
- : getSource(webmention);
146
- if (!map.has(key)) {
147
- map.set(key, webmention);
148
- }
149
- return map;
150
- }, new Map())
151
- .values(),
152
- ];
153
- };
154
-
155
- const processBlocklist = (webmentions, blocklist) => {
280
+ export const getByType = getByTypes;
281
+ export const getWebmentionsByTypes = getByTypes;
282
+ export const getWebmentionsByType = getByTypes;
283
+
284
+ /**
285
+ * @param {Webmention[]} webmentions
286
+ * @param {string[]} blocklist
287
+ * @returns {Webmention[]}
288
+ */
289
+ export const processBlocklist = (webmentions, blocklist) => {
156
290
  return webmentions.filter((webmention) => {
157
291
  let url = getSource(webmention);
158
292
  let source = getSource(webmention);
@@ -167,8 +301,15 @@ const processBlocklist = (webmentions, blocklist) => {
167
301
  return true;
168
302
  });
169
303
  };
170
-
171
- const processAllowlist = (webmentions, allowlist) => {
304
+ export const processWebmentionBlocklist = processBlocklist;
305
+ export const processWebmentionsBlocklist = processBlocklist;
306
+
307
+ /**
308
+ * @param {Webmention[]} webmentions
309
+ * @param {string[]} allowlist
310
+ * @returns {Webmention[]}
311
+ */
312
+ export const processAllowlist = (webmentions, allowlist) => {
172
313
  return webmentions.filter((webmention) => {
173
314
  let url = getSource(webmention);
174
315
  let source = getSource(webmention);
@@ -183,8 +324,16 @@ const processAllowlist = (webmentions, allowlist) => {
183
324
  return false;
184
325
  });
185
326
  };
186
-
187
- const performFetch = async (options, webmentions, url) => {
327
+ export const processWebmentionAllowlist = processAllowlist;
328
+ export const processWebmentionsAllowlist = processAllowlist;
329
+
330
+ /**
331
+ * @param {Options} options
332
+ * @param {Webmention[]} webmentions
333
+ * @param {string} url
334
+ * @returns {Promise<{found: number, webmentions: Webmention[]}>}
335
+ */
336
+ export const fetchWebmentions = async (options, webmentions, url) => {
188
337
  return await fetch(url)
189
338
  .then(async (response) => {
190
339
  if (!response.ok) {
@@ -193,7 +342,7 @@ const performFetch = async (options, webmentions, url) => {
193
342
 
194
343
  const feed = await response.json();
195
344
 
196
- if (!options.key in feed) {
345
+ if (!(options.key in feed)) {
197
346
  console.log(
198
347
  `${styleText("grey", `[${hostname(options.domain)}]`)} ${
199
348
  options.key
@@ -224,7 +373,7 @@ const performFetch = async (options, webmentions, url) => {
224
373
 
225
374
  return {
226
375
  found: feed[options.key].length,
227
- filtered: webmentions,
376
+ webmentions: webmentions,
228
377
  };
229
378
  })
230
379
  .catch((error) => {
@@ -241,12 +390,16 @@ const performFetch = async (options, webmentions, url) => {
241
390
 
242
391
  return {
243
392
  found: 0,
244
- filtered: webmentions,
393
+ webmentions: webmentions,
245
394
  };
246
395
  });
247
396
  };
248
397
 
249
- const fetchWebmentions = async (options) => {
398
+ /**
399
+ * @param {Options} options
400
+ * @returns {Promise<Webmention[]>}
401
+ */
402
+ export const retrieveWebmentions = async (options) => {
250
403
  if (!options.domain) {
251
404
  throw new Error(
252
405
  "`domain` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.",
@@ -267,7 +420,7 @@ const fetchWebmentions = async (options) => {
267
420
 
268
421
  let asset = new AssetCache(
269
422
  options.uniqueKey || `webmentions-${hostname(options.domain)}`,
270
- options.directory,
423
+ options.cacheDirectory,
271
424
  );
272
425
 
273
426
  let webmentions = [];
@@ -307,14 +460,14 @@ const fetchWebmentions = async (options) => {
307
460
  while (true) {
308
461
  const urlPaginated =
309
462
  urlObject.href + `&per-page=${perPage}&page=${page}`;
310
- const fetched = await performFetch(
463
+ const fetched = await fetchWebmentions(
311
464
  options,
312
465
  webmentions,
313
466
  urlPaginated,
314
467
  );
315
468
 
316
469
  // An error occurred during fetching paged results → break
317
- if (!fetched && !fetched.found && !fetched.filtered) {
470
+ if (!fetched && !fetched.found && !fetched.webmentions) {
318
471
  break;
319
472
  }
320
473
 
@@ -323,7 +476,7 @@ const fetchWebmentions = async (options) => {
323
476
  break;
324
477
  }
325
478
 
326
- webmentions = fetched.filtered;
479
+ webmentions = fetched.webmentions;
327
480
 
328
481
  // If there are less Webmentions found than should be in each
329
482
  // page → break
@@ -337,8 +490,8 @@ const fetchWebmentions = async (options) => {
337
490
  await new Promise((resolve) => setTimeout(resolve, 1000));
338
491
  }
339
492
  } else {
340
- const fetched = await performFetch(options, webmentions, url);
341
- webmentions = fetched.filtered;
493
+ const fetched = await fetchWebmentions(options, webmentions, url);
494
+ webmentions = fetched.webmentions;
342
495
  }
343
496
 
344
497
  // Process the blocklist, if it has any entries
@@ -376,36 +529,50 @@ const fetchWebmentions = async (options) => {
376
529
  return webmentions;
377
530
  };
378
531
 
379
- let filtered = {};
380
- const filteredWebmentions = async (options) => {
381
- if (Object.entries(filtered).length) {
382
- return filtered;
532
+ /** @type {Webmention[]} */
533
+ const WEBMENTIONS = {};
534
+
535
+ /**
536
+ * @param {Options} options
537
+ * @returns {Promise<Object<string, Webmention[]>>}
538
+ */
539
+ export const webmentionsByURL = async (options) => {
540
+ if (Object.keys(WEBMENTIONS).length) {
541
+ return WEBMENTIONS;
383
542
  }
384
543
 
385
- let rawWebmentions = await fetchWebmentions(options);
544
+ let rawWebmentions = await retrieveWebmentions(options);
386
545
 
387
546
  // Fix local URLs based on urlReplacements and sort Webmentions into groups
388
547
  // by target base URL
389
548
  rawWebmentions.forEach((webmention) => {
390
- let url = baseUrl(
391
- fixUrl(
549
+ let url = baseURL(
550
+ fixURL(
392
551
  getTarget(webmention).replace(/\/?$/, "/"),
393
552
  options.urlReplacements,
394
553
  ),
395
554
  );
396
555
 
397
- if (!filtered[url]) {
398
- filtered[url] = [];
556
+ if (!WEBMENTIONS[url]) {
557
+ WEBMENTIONS[url] = [];
399
558
  }
400
559
 
401
- filtered[url].push(webmention);
560
+ WEBMENTIONS[url].push(webmention);
402
561
  });
403
562
 
404
- return filtered;
563
+ return WEBMENTIONS;
405
564
  };
406
-
407
- const getWebmentions = async (options, url, allowedTypes = {}) => {
408
- const webmentions = await filteredWebmentions(options);
565
+ export const webmentionsByUrl = webmentionsByURL;
566
+ export const filteredWebmentions = webmentionsByURL;
567
+
568
+ /**
569
+ * @param {Options} options
570
+ * @param {string} url
571
+ * @param {WebmentionType[]|WebmentionType} [types=[]]
572
+ * @returns {Promise<Webmention[]>}
573
+ */
574
+ export const getWebmentions = async (options, url, types = []) => {
575
+ const webmentions = await webmentionsByURL(options);
409
576
  url = absoluteURL(url, options.domain);
410
577
 
411
578
  if (!url || !webmentions || !webmentions[url]) {
@@ -416,9 +583,10 @@ const getWebmentions = async (options, url, allowedTypes = {}) => {
416
583
  webmentions[url]
417
584
  // Filter webmentions by allowed response post types
418
585
  .filter((entry) => {
419
- return typeof allowedTypes === "object" &&
420
- Object.keys(allowedTypes).length
421
- ? allowedTypes.includes(getType(entry))
586
+ return typeof types === "object" && Object.keys(types).length
587
+ ? types.includes(getType(entry))
588
+ : typeof types === "string"
589
+ ? types === getType(entry)
422
590
  : true;
423
591
  })
424
592
  // Sanitize content of webmentions against HTML limit
@@ -448,25 +616,28 @@ const getWebmentions = async (options, url, allowedTypes = {}) => {
448
616
  );
449
617
  };
450
618
 
451
- const eleventyCacheWebmentions = (eleventyConfig, options = {}) => {
619
+ /**
620
+ * @param {Object} eleventyConfig
621
+ * @param {Options} [options={}]
622
+ */
623
+ export const eleventyCacheWebmentions = (eleventyConfig, options = {}) => {
452
624
  options = Object.assign(defaults, options);
453
625
 
626
+ const byURL = webmentionsByURL(options);
627
+ const all = Object.values(byURL).reduce(
628
+ (array, webmentions) => [...array, ...webmentions],
629
+ [],
630
+ );
631
+
454
632
  // Global Data
455
633
  eleventyConfig.addGlobalData("webmentionsDefaults", defaults);
456
634
  eleventyConfig.addGlobalData("webmentionsOptions", options);
457
- const filtered = async () => await filteredWebmentions(options);
458
- eleventyConfig.addGlobalData("webmentionsByUrl", filtered);
459
- const unfiltered = async () =>
460
- await filteredWebmentions(options).then((filtered) =>
461
- Object.values(filtered).reduce(
462
- (array, webmentions) => [...array, ...webmentions],
463
- [],
464
- ),
465
- );
466
- eleventyConfig.addGlobalData("webmentionsAll", unfiltered);
635
+ eleventyConfig.addGlobalData("webmentionsByURL", byURL);
636
+ eleventyConfig.addGlobalData("webmentionsByUrl", byURL);
637
+ eleventyConfig.addGlobalData("webmentionsAll", all);
467
638
 
468
639
  // Liquid Filters
469
- eleventyConfig.addLiquidFilter("getWebmentionsByType", getByType);
640
+ eleventyConfig.addLiquidFilter("getWebmentionsByType", getByTypes);
470
641
  eleventyConfig.addLiquidFilter("getWebmentionsByTypes", getByTypes);
471
642
  eleventyConfig.addLiquidFilter("getWebmentionPublished", getPublished);
472
643
  eleventyConfig.addLiquidFilter("getWebmentionReceived", getReceived);
@@ -477,7 +648,7 @@ const eleventyCacheWebmentions = (eleventyConfig, options = {}) => {
477
648
  eleventyConfig.addLiquidFilter("getWebmentionType", getType);
478
649
 
479
650
  // Nunjucks Filters
480
- eleventyConfig.addNunjucksFilter("getWebmentionsByType", getByType);
651
+ eleventyConfig.addNunjucksFilter("getWebmentionsByType", getByTypes);
481
652
  eleventyConfig.addNunjucksFilter("getWebmentionsByTypes", getByTypes);
482
653
  eleventyConfig.addNunjucksFilter("getWebmentionPublished", getPublished);
483
654
  eleventyConfig.addNunjucksFilter("getWebmentionReceived", getReceived);
@@ -488,28 +659,4 @@ const eleventyCacheWebmentions = (eleventyConfig, options = {}) => {
488
659
  eleventyConfig.addNunjucksFilter("getWebmentionType", getType);
489
660
  };
490
661
 
491
- module.exports = eleventyCacheWebmentions;
492
- module.exports.defaults = defaults;
493
- module.exports.filteredWebmentions = filteredWebmentions;
494
- module.exports.webmentionsByUrl = filteredWebmentions;
495
- module.exports.fetchWebmentions = fetchWebmentions;
496
- module.exports.performFetch = performFetch;
497
- module.exports.getWebmentions = getWebmentions;
498
- module.exports.getByType = getByType;
499
- module.exports.getWebmentionsByType = getByType;
500
- module.exports.getByTypes = getByTypes;
501
- module.exports.getWebmentionsByTypes = getByTypes;
502
- module.exports.getPublished = getPublished;
503
- module.exports.getWebmentionPublished = getPublished;
504
- module.exports.getReceived = getReceived;
505
- module.exports.getWebmentionReceived = getReceived;
506
- module.exports.getContent = getContent;
507
- module.exports.getWebmentionContent = getContent;
508
- module.exports.getSource = getSource;
509
- module.exports.getWebmentionSource = getSource;
510
- module.exports.getURL = getURL;
511
- module.exports.getWebmentionURL = getURL;
512
- module.exports.getTarget = getTarget;
513
- module.exports.getWebmentionTarget = getTarget;
514
- module.exports.getType = getType;
515
- module.exports.getWebmentionType = getType;
662
+ export default eleventyCacheWebmentions;
@@ -0,0 +1 @@
1
+ const{AssetCache}=require("@11ty/eleventy-fetch"),{styleText}=require("node:util"),sanitizeHTML=require("sanitize-html"),defaults={refresh:!1,duration:"1d",uniqueKey:"webmentions",cacheDirectory:void 0,allowedHTML:{allowedTags:["a","b","em","i","strong"],allowedAttributes:{a:["href"]}},allowlist:[],blocklist:[],urlReplacements:{},maximumHtmlLength:1e3,maximumHtmlText:"mentioned this in"},absoluteURL=(url,domain)=>{try{return new URL(url,domain).toString()}catch{return console.error(`Trying to convert ${styleText("bold",url)} to be an absolute url with base ${styleText("bold",domain)} and failed.`),url}},baseURL=url=>url.split("#")[0].split("?")[0],fixURL=(url,urlReplacements)=>Object.entries(urlReplacements).reduce((accumulator,[key,value])=>{const regex=new RegExp(key,"g");return accumulator.replace(regex,value)},url),hostname=url=>typeof url=="string"&&url.includes("//")?new URL(url).hostname:url,epoch=date=>new Date(date).getTime(),removeDuplicates=webmentions=>[...webmentions.reduce((map,webmention)=>{const key=webmention==null?webmention:getSource(webmention);return map.has(key)||map.set(key,webmention),map},new Map).values()],getPublished=webmention=>webmention?.data?.published||webmention.published||webmention["wm-received"]||webmention.verified_date,getReceived=webmention=>webmention["wm-received"]||webmention.verified_date||webmention.published||webmention?.data?.published,getContent=webmention=>webmention?.contentSanitized||webmention?.content?.html||webmention?.content?.value||webmention?.content||webmention?.data?.content||"",getSource=webmention=>webmention["wm-source"]||webmention.source||webmention?.data?.url||webmention.url,getURL=webmention=>webmention?.data?.url||webmention.url||webmention["wm-source"]||webmention.source,getTarget=webmention=>webmention["wm-target"]||webmention.target,getType=webmention=>webmention["wm-property"]||webmention?.activity?.type||webmention.type,getByTypes=(webmentions,types)=>webmentions.filter(webmention=>typeof types=="string"?types===getType(webmention):types.includes(getType(webmention))),processBlocklist=(webmentions,blocklist)=>webmentions.filter(webmention=>{let url=getSource(webmention),source=getSource(webmention);for(let blocklistURL of blocklist)if(url.includes(blocklistURL.replace(/\/?$/,"/"))||source.includes(blocklistURL.replace(/\/?$/,"/")))return!1;return!0}),processAllowlist=(webmentions,allowlist)=>webmentions.filter(webmention=>{let url=getSource(webmention),source=getSource(webmention);for(let allowlistURL of allowlist)if(url.includes(allowlistURL.replace(/\/?$/,"/"))||source.includes(allowlistURL.replace(/\/?$/,"/")))return!0;return!1}),fetchWebmentions=async(options,webmentions,url)=>await fetch(url).then(async response=>{if(!response.ok)return Promise.reject(response);const feed=await response.json();return options.key in feed?(webmentions=feed[options.key].concat(webmentions),webmentions=removeDuplicates(webmentions),options.blocklist.length&&(webmentions=processBlocklist(webmentions,options.blocklist)),options.allowlist.length&&(webmentions=processAllowlist(webmentions,options.allowlist)),webmentions=webmentions.sort((a,b)=>epoch(getReceived(b))-epoch(getReceived(a))),{found:feed[options.key].length,webmentions}):(console.log(`${styleText("grey",`[${hostname(options.domain)}]`)} ${options.key} was not found as a key in the response from ${styleText("bold",hostname(options.feed))}!`),Promise.reject(response))}).catch(error=>(console.warn(`${styleText("grey",`[${hostname(options.domain)}]`)} Something went wrong with your Webmention request to ${styleText("bold",hostname(options.feed))}!`),console.warn(error instanceof Error?error.message:error),{found:0,webmentions})),retrieveWebmentions=async options=>{if(!options.domain)throw new Error("`domain` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.");if(!options.feed)throw new Error("`feed` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.");if(!options.key)throw new Error("`key` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.");let asset=new AssetCache(options.uniqueKey||`webmentions-${hostname(options.domain)}`,options.cacheDirectory),webmentions=[];asset.isCacheValid("9001y")&&!options.refresh&&(webmentions=await asset.getCachedValue());const webmentionsCachedLength=webmentions.length;if(!asset.isCacheValid(options.refresh?"0s":options.duration)){const performanceStart=process.hrtime(),since=webmentions.length?getReceived(webmentions[0]):!1,url=`${options.feed}${since?`${options.feed.includes("?")?"&":"?"}since=${since}`:""}`;if(url.includes("https://webmention.io")){const urlObject=new URL(url),perPage=Number(urlObject.searchParams.get("per-page"))||1e3;urlObject.searchParams.delete("per-page");let page=0;for(;;){const urlPaginated=urlObject.href+`&per-page=${perPage}&page=${page}`,fetched=await fetchWebmentions(options,webmentions,urlPaginated);if(!fetched&&!fetched.found&&!fetched.webmentions||fetched.found===0||(webmentions=fetched.webmentions,fetched.found<perPage))break;page+=1,await new Promise(resolve=>setTimeout(resolve,1e3))}}else webmentions=(await fetchWebmentions(options,webmentions,url)).webmentions;options.blocklist.length&&(webmentions=processBlocklist(webmentions,options.blocklist)),options.allowlist.length&&(webmentions=processAllowlist(webmentions,options.allowlist)),await asset.save(webmentions,"json");const performance=process.hrtime(performanceStart);webmentionsCachedLength<webmentions.length&&console.log(`${styleText("grey",`[${hostname(options.domain)}]`)} ${styleText("bold",String(webmentions.length-webmentionsCachedLength))} new Webmentions fetched into cache in ${styleText("bold",(performance[0]+performance[1]/1e9).toFixed(3)+" seconds")}.`)}return webmentions},WEBMENTIONS={},webmentionsByURL=async options=>(Object.keys(WEBMENTIONS).length||(await retrieveWebmentions(options)).forEach(webmention=>{let url=baseURL(fixURL(getTarget(webmention).replace(/\/?$/,"/"),options.urlReplacements));WEBMENTIONS[url]||(WEBMENTIONS[url]=[]),WEBMENTIONS[url].push(webmention)}),WEBMENTIONS),getWebmentions=async(options,url,types=[])=>{const webmentions=await webmentionsByURL(options);return url=absoluteURL(url,options.domain),!url||!webmentions||!webmentions[url]?[]:webmentions[url].filter(entry=>typeof types=="object"&&Object.keys(types).length?types.includes(getType(entry)):typeof types=="string"?types===getType(entry):!0).map(entry=>{const html=getContent(entry);return html.length&&(entry.contentSanitized=sanitizeHTML(html,options.allowedHTML),html.length>options.maximumHtmlLength&&(entry.contentSanitized=`${options.maximumHtmlText} <a href="${getSource(entry)}">${getSource(entry)}</a>`)),entry}).sort((a,b)=>epoch(getPublished(a))-epoch(getPublished(b)))},eleventyCacheWebmentions=(eleventyConfig,options={})=>{options=Object.assign(defaults,options);const byURL=webmentionsByURL(options),all=Object.values(byURL).reduce((array,webmentions)=>[...array,...webmentions],[]);eleventyConfig.addGlobalData("webmentionsDefaults",defaults),eleventyConfig.addGlobalData("webmentionsOptions",options),eleventyConfig.addGlobalData("webmentionsByURL",byURL),eleventyConfig.addGlobalData("webmentionsByUrl",byURL),eleventyConfig.addGlobalData("webmentionsAll",all),eleventyConfig.addLiquidFilter("getWebmentionsByType",getByTypes),eleventyConfig.addLiquidFilter("getWebmentionsByTypes",getByTypes),eleventyConfig.addLiquidFilter("getWebmentionPublished",getPublished),eleventyConfig.addLiquidFilter("getWebmentionReceived",getReceived),eleventyConfig.addLiquidFilter("getWebmentionContent",getContent),eleventyConfig.addLiquidFilter("getWebmentionSource",getSource),eleventyConfig.addLiquidFilter("getWebmentionURL",getURL),eleventyConfig.addLiquidFilter("getWebmentionTarget",getTarget),eleventyConfig.addLiquidFilter("getWebmentionType",getType),eleventyConfig.addNunjucksFilter("getWebmentionsByType",getByTypes),eleventyConfig.addNunjucksFilter("getWebmentionsByTypes",getByTypes),eleventyConfig.addNunjucksFilter("getWebmentionPublished",getPublished),eleventyConfig.addNunjucksFilter("getWebmentionReceived",getReceived),eleventyConfig.addNunjucksFilter("getWebmentionContent",getContent),eleventyConfig.addNunjucksFilter("getWebmentionSource",getSource),eleventyConfig.addNunjucksFilter("getWebmentionURL",getURL),eleventyConfig.addNunjucksFilter("getWebmentionTarget",getTarget),eleventyConfig.addNunjucksFilter("getWebmentionType",getType)};module.exports=eleventyCacheWebmentions,module.exports.defaults=defaults,module.exports.getPublished=getPublished,module.exports.getWebmentionPublished=getPublished,module.exports.getReceived=getReceived,module.exports.getWebmentionReceived=getReceived,module.exports.getContent=getContent,module.exports.getWebmentionContent=getContent,module.exports.getSource=getSource,module.exports.getWebmentionSource=getSource,module.exports.getURL=getURL,module.exports.getWebmentionURL=getURL,module.exports.getTarget=getTarget,module.exports.getWebmentionTarget=getTarget,module.exports.getType=getType,module.exports.getWebmentionType=getType,module.exports.getByTypes=getByTypes,module.exports.getByType=getByTypes,module.exports.getWebmentionsByTypes=getByTypes,module.exports.getWebmentionsByType=getByTypes,module.exports.processBlocklist=processBlocklist,module.exports.processWebmentionBlocklist=processBlocklist,module.exports.processWebmentionsBlocklist=processBlocklist,module.exports.processAllowlist=processAllowlist,module.exports.processWebmentionAllowlist=processAllowlist,module.exports.processWebmentionsAllowlist=processAllowlist,module.exports.fetchWebmentions=fetchWebmentions,module.exports.retrieveWebmentions=retrieveWebmentions,module.exports.webmentionsByURL=webmentionsByURL,module.exports.webmentionsByUrl=webmentionsByURL,module.exports.filteredWebmentions=webmentionsByURL,module.exports.getWebmentions=getWebmentions;
@@ -0,0 +1 @@
1
+ import{AssetCache}from"@11ty/eleventy-fetch";import{styleText}from"node:util";import sanitizeHTML from"sanitize-html";export const defaults={refresh:!1,duration:"1d",uniqueKey:"webmentions",cacheDirectory:void 0,allowedHTML:{allowedTags:["a","b","em","i","strong"],allowedAttributes:{a:["href"]}},allowlist:[],blocklist:[],urlReplacements:{},maximumHtmlLength:1e3,maximumHtmlText:"mentioned this in"};const absoluteURL=(url,domain)=>{try{return new URL(url,domain).toString()}catch{return console.error(`Trying to convert ${styleText("bold",url)} to be an absolute url with base ${styleText("bold",domain)} and failed.`),url}},baseURL=url=>url.split("#")[0].split("?")[0],fixURL=(url,urlReplacements)=>Object.entries(urlReplacements).reduce((accumulator,[key,value])=>{const regex=new RegExp(key,"g");return accumulator.replace(regex,value)},url),hostname=url=>typeof url=="string"&&url.includes("//")?new URL(url).hostname:url,epoch=date=>new Date(date).getTime(),removeDuplicates=webmentions=>[...webmentions.reduce((map,webmention)=>{const key=webmention==null?webmention:getSource(webmention);return map.has(key)||map.set(key,webmention),map},new Map).values()];export const getPublished=webmention=>webmention?.data?.published||webmention.published||webmention["wm-received"]||webmention.verified_date,getWebmentionPublished=getPublished,getReceived=webmention=>webmention["wm-received"]||webmention.verified_date||webmention.published||webmention?.data?.published,getWebmentionReceived=getReceived,getContent=webmention=>webmention?.contentSanitized||webmention?.content?.html||webmention?.content?.value||webmention?.content||webmention?.data?.content||"",getWebmentionContent=getContent,getSource=webmention=>webmention["wm-source"]||webmention.source||webmention?.data?.url||webmention.url,getWebmentionSource=getSource,getURL=webmention=>webmention?.data?.url||webmention.url||webmention["wm-source"]||webmention.source,getWebmentionURL=getURL,getTarget=webmention=>webmention["wm-target"]||webmention.target,getWebmentionTarget=getTarget,getType=webmention=>webmention["wm-property"]||webmention?.activity?.type||webmention.type,getWebmentionType=getType,getByTypes=(webmentions,types)=>webmentions.filter(webmention=>typeof types=="string"?types===getType(webmention):types.includes(getType(webmention))),getByType=getByTypes,getWebmentionsByTypes=getByTypes,getWebmentionsByType=getByTypes,processBlocklist=(webmentions,blocklist)=>webmentions.filter(webmention=>{let url=getSource(webmention),source=getSource(webmention);for(let blocklistURL of blocklist)if(url.includes(blocklistURL.replace(/\/?$/,"/"))||source.includes(blocklistURL.replace(/\/?$/,"/")))return!1;return!0}),processWebmentionBlocklist=processBlocklist,processWebmentionsBlocklist=processBlocklist,processAllowlist=(webmentions,allowlist)=>webmentions.filter(webmention=>{let url=getSource(webmention),source=getSource(webmention);for(let allowlistURL of allowlist)if(url.includes(allowlistURL.replace(/\/?$/,"/"))||source.includes(allowlistURL.replace(/\/?$/,"/")))return!0;return!1}),processWebmentionAllowlist=processAllowlist,processWebmentionsAllowlist=processAllowlist,fetchWebmentions=async(options,webmentions,url)=>await fetch(url).then(async response=>{if(!response.ok)return Promise.reject(response);const feed=await response.json();return options.key in feed?(webmentions=feed[options.key].concat(webmentions),webmentions=removeDuplicates(webmentions),options.blocklist.length&&(webmentions=processBlocklist(webmentions,options.blocklist)),options.allowlist.length&&(webmentions=processAllowlist(webmentions,options.allowlist)),webmentions=webmentions.sort((a,b)=>epoch(getReceived(b))-epoch(getReceived(a))),{found:feed[options.key].length,webmentions}):(console.log(`${styleText("grey",`[${hostname(options.domain)}]`)} ${options.key} was not found as a key in the response from ${styleText("bold",hostname(options.feed))}!`),Promise.reject(response))}).catch(error=>(console.warn(`${styleText("grey",`[${hostname(options.domain)}]`)} Something went wrong with your Webmention request to ${styleText("bold",hostname(options.feed))}!`),console.warn(error instanceof Error?error.message:error),{found:0,webmentions})),retrieveWebmentions=async options=>{if(!options.domain)throw new Error("`domain` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.");if(!options.feed)throw new Error("`feed` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.");if(!options.key)throw new Error("`key` is a required field when attempting to retrieve Webmentions. See https://www.npmjs.com/package/@chrisburnell/eleventy-cache-webmentions#installation for more information.");let asset=new AssetCache(options.uniqueKey||`webmentions-${hostname(options.domain)}`,options.cacheDirectory),webmentions=[];asset.isCacheValid("9001y")&&!options.refresh&&(webmentions=await asset.getCachedValue());const webmentionsCachedLength=webmentions.length;if(!asset.isCacheValid(options.refresh?"0s":options.duration)){const performanceStart=process.hrtime(),since=webmentions.length?getReceived(webmentions[0]):!1,url=`${options.feed}${since?`${options.feed.includes("?")?"&":"?"}since=${since}`:""}`;if(url.includes("https://webmention.io")){const urlObject=new URL(url),perPage=Number(urlObject.searchParams.get("per-page"))||1e3;urlObject.searchParams.delete("per-page");let page=0;for(;;){const urlPaginated=urlObject.href+`&per-page=${perPage}&page=${page}`,fetched=await fetchWebmentions(options,webmentions,urlPaginated);if(!fetched&&!fetched.found&&!fetched.webmentions||fetched.found===0||(webmentions=fetched.webmentions,fetched.found<perPage))break;page+=1,await new Promise(resolve=>setTimeout(resolve,1e3))}}else webmentions=(await fetchWebmentions(options,webmentions,url)).webmentions;options.blocklist.length&&(webmentions=processBlocklist(webmentions,options.blocklist)),options.allowlist.length&&(webmentions=processAllowlist(webmentions,options.allowlist)),await asset.save(webmentions,"json");const performance=process.hrtime(performanceStart);webmentionsCachedLength<webmentions.length&&console.log(`${styleText("grey",`[${hostname(options.domain)}]`)} ${styleText("bold",String(webmentions.length-webmentionsCachedLength))} new Webmentions fetched into cache in ${styleText("bold",(performance[0]+performance[1]/1e9).toFixed(3)+" seconds")}.`)}return webmentions};const WEBMENTIONS={};export const webmentionsByURL=async options=>(Object.keys(WEBMENTIONS).length||(await retrieveWebmentions(options)).forEach(webmention=>{let url=baseURL(fixURL(getTarget(webmention).replace(/\/?$/,"/"),options.urlReplacements));WEBMENTIONS[url]||(WEBMENTIONS[url]=[]),WEBMENTIONS[url].push(webmention)}),WEBMENTIONS),webmentionsByUrl=webmentionsByURL,filteredWebmentions=webmentionsByURL,getWebmentions=async(options,url,types=[])=>{const webmentions=await webmentionsByURL(options);return url=absoluteURL(url,options.domain),!url||!webmentions||!webmentions[url]?[]:webmentions[url].filter(entry=>typeof types=="object"&&Object.keys(types).length?types.includes(getType(entry)):typeof types=="string"?types===getType(entry):!0).map(entry=>{const html=getContent(entry);return html.length&&(entry.contentSanitized=sanitizeHTML(html,options.allowedHTML),html.length>options.maximumHtmlLength&&(entry.contentSanitized=`${options.maximumHtmlText} <a href="${getSource(entry)}">${getSource(entry)}</a>`)),entry}).sort((a,b)=>epoch(getPublished(a))-epoch(getPublished(b)))},eleventyCacheWebmentions=(eleventyConfig,options={})=>{options=Object.assign(defaults,options);const byURL=webmentionsByURL(options),all=Object.values(byURL).reduce((array,webmentions)=>[...array,...webmentions],[]);eleventyConfig.addGlobalData("webmentionsDefaults",defaults),eleventyConfig.addGlobalData("webmentionsOptions",options),eleventyConfig.addGlobalData("webmentionsByURL",byURL),eleventyConfig.addGlobalData("webmentionsByUrl",byURL),eleventyConfig.addGlobalData("webmentionsAll",all),eleventyConfig.addLiquidFilter("getWebmentionsByType",getByTypes),eleventyConfig.addLiquidFilter("getWebmentionsByTypes",getByTypes),eleventyConfig.addLiquidFilter("getWebmentionPublished",getPublished),eleventyConfig.addLiquidFilter("getWebmentionReceived",getReceived),eleventyConfig.addLiquidFilter("getWebmentionContent",getContent),eleventyConfig.addLiquidFilter("getWebmentionSource",getSource),eleventyConfig.addLiquidFilter("getWebmentionURL",getURL),eleventyConfig.addLiquidFilter("getWebmentionTarget",getTarget),eleventyConfig.addLiquidFilter("getWebmentionType",getType),eleventyConfig.addNunjucksFilter("getWebmentionsByType",getByTypes),eleventyConfig.addNunjucksFilter("getWebmentionsByTypes",getByTypes),eleventyConfig.addNunjucksFilter("getWebmentionPublished",getPublished),eleventyConfig.addNunjucksFilter("getWebmentionReceived",getReceived),eleventyConfig.addNunjucksFilter("getWebmentionContent",getContent),eleventyConfig.addNunjucksFilter("getWebmentionSource",getSource),eleventyConfig.addNunjucksFilter("getWebmentionURL",getURL),eleventyConfig.addNunjucksFilter("getWebmentionTarget",getTarget),eleventyConfig.addNunjucksFilter("getWebmentionType",getType)};export default eleventyCacheWebmentions;
@@ -0,0 +1,36 @@
1
+ import js from "@eslint/js";
2
+
3
+ export default [
4
+ js.configs.recommended,
5
+ {
6
+ files: ["src/**/*.js"],
7
+ languageOptions: {
8
+ ecmaVersion: "latest",
9
+ },
10
+ rules: {
11
+ "no-process-env": 0,
12
+ "jsdoc/check-access": "warn",
13
+ "jsdoc/check-alignment": "warn",
14
+ "jsdoc/check-indentation": "warn",
15
+ "jsdoc/check-param-names": "warn",
16
+ "jsdoc/check-tag-names": "warn",
17
+ "jsdoc/check-types": "warn",
18
+ "jsdoc/implements-on-classes": "warn",
19
+ "jsdoc/no-undefined-types": "warn",
20
+ "jsdoc/require-jsdoc": [
21
+ "warn",
22
+ {
23
+ require: {
24
+ FunctionDeclaration: true,
25
+ MethodDefinition: true,
26
+ ClassDeclaration: true,
27
+ },
28
+ },
29
+ ],
30
+ "jsdoc/require-param": "warn",
31
+ "jsdoc/require-param-type": "warn",
32
+ "jsdoc/require-returns": "warn",
33
+ "jsdoc/require-returns-type": "warn",
34
+ },
35
+ },
36
+ ];