@chrisburnell/eleventy-cache-webmentions 0.2.0 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ const fetch = require("node-fetch")
2
2
  const sanitizeHTML = require("sanitize-html")
3
3
  const uniqBy = require("lodash/uniqBy")
4
4
  const { AssetCache } = require("@11ty/eleventy-fetch")
5
+ const { defaultsDeep } = require("lodash")
5
6
 
6
7
  const absoluteURL = (url, domain) => {
7
8
  try {
@@ -37,161 +38,180 @@ const epoch = (value) => {
37
38
  return new Date(value).getTime()
38
39
  }
39
40
 
40
- module.exports = (eleventyConfig, options = {}) => {
41
- options = Object.assign(
42
- {
43
- uniqueKey: "webmentions",
44
- allowedHTML: {
45
- allowedTags: ["b", "i", "em", "strong", "a"],
46
- allowedAttributes: {
47
- a: ["href"],
48
- },
49
- },
50
- urlReplacements: {},
51
- maximumHtmlLength: 2000,
52
- maximumHtmlText: "mentioned this in",
53
- },
54
- options
55
- )
41
+ const getPublished = (webmention) => {
42
+ return webmention["wm-received"] || webmention?.["data"]["published"] || webmention["verified_date"] || webmention["published"]
43
+ }
56
44
 
57
- const fetchWebmentions = async () => {
58
- let asset = new AssetCache(options.uniqueKey)
59
- asset.ensureDir()
45
+ const getContent = (webmention) => {
46
+ return webmention?.["content"]["html"] || webmention["content"] || webmention?.["data"]["content"]
47
+ }
60
48
 
61
- let webmentions = []
49
+ const getSource = (webmention) => {
50
+ return webmention["url"] || webmention?.["data"]["url"] || webmention["wm-source"] || webmention["source"]
51
+ }
62
52
 
63
- // If there is a cached file at all, grab its contents now
64
- if (asset.isCacheValid("9001y")) {
65
- webmentions = await asset.getCachedValue()
66
- }
53
+ const getTarget = (webmention) => {
54
+ return webmention["wm-target"] || webmention["target"]
55
+ }
67
56
 
68
- // If there is a cached file but it is outside of expiry, fetch fresh
69
- // results since the most recent webmention
70
- if (!asset.isCacheValid(options.duration)) {
71
- const since = webmentions.length ? webmentions[0]["wm-received"] || webmentions[0]["published"] : false
72
- const url = `${options.feed}${since && options.feed.includes("https://webmention.io") ? `&since=${since}` : ""}`
73
- await fetch(url)
74
- .then(async (response) => {
75
- if (response.ok) {
76
- const feed = await response.json()
77
- if (feed[options.key].length) {
78
- webmentions = uniqBy([...feed[options.key], ...webmentions], (item) => {
79
- return item["wm-source"] || item["source"]
80
- })
81
- console.log(`[${hostname(options.domain)}] ${feed[options.key].length} new Webmentions fetched into cache.`)
82
- }
83
- await asset.save(webmentions, "json")
84
- return webmentions
85
- }
86
- return Promise.reject(response)
87
- })
88
- .catch((error) => {
89
- console.log(`[${hostname(options.domain)}] Something went wrong with your request to ${hostname(options.feed)}!`, error)
90
- })
91
- }
57
+ const getType = (webmention) => {
58
+ return webmention["wm-property"] || webmention?.["activity"]["type"] || webmention["type"]
59
+ }
60
+
61
+ const defaults = {
62
+ uniqueKey: "webmentions",
63
+ allowedHTML: {
64
+ allowedTags: ["b", "i", "em", "strong", "a"],
65
+ allowedAttributes: {
66
+ a: ["href"],
67
+ },
68
+ },
69
+ urlReplacements: {},
70
+ maximumHtmlLength: 2000,
71
+ maximumHtmlText: "mentioned this in",
72
+ }
73
+
74
+ const fetchWebmentions = async (options) => {
75
+ let asset = new AssetCache(options.uniqueKey)
76
+ asset.ensureDir()
92
77
 
93
- return webmentions
78
+ let webmentions = []
79
+
80
+ // If there is a cached file at all, grab its contents now
81
+ if (asset.isCacheValid("9001y")) {
82
+ webmentions = await asset.getCachedValue()
94
83
  }
95
84
 
96
- const filteredWebmentions = async () => {
97
- const rawWebmentions = await fetchWebmentions()
98
- let webmentions = {}
85
+ // If there is a cached file but it is outside of expiry, fetch fresh
86
+ // results since the most recent webmention
87
+ if (!asset.isCacheValid(options.duration)) {
88
+ const since = webmentions.length ? getPublished(webmentions[0]) : false
89
+ const url = `${options.feed}${since ? `${options.feed.includes("?") ? "&" : "?"}since=${since}` : ""}`
90
+ await fetch(url)
91
+ .then(async (response) => {
92
+ if (response.ok) {
93
+ const feed = await response.json()
94
+ if (feed[options.key].length) {
95
+ webmentions = uniqBy([...feed[options.key], ...webmentions], (entry) => {
96
+ return getSource(entry)
97
+ })
98
+ console.log(`[${hostname(options.domain)}] ${feed[options.key].length} new Webmentions fetched into cache.`)
99
+ }
100
+ await asset.save(webmentions, "json")
101
+ return webmentions
102
+ }
103
+ return Promise.reject(response)
104
+ })
105
+ .catch((error) => {
106
+ console.log(`[${hostname(options.domain)}] Something went wrong with your request to ${hostname(options.feed)}!`, error)
107
+ })
108
+ }
99
109
 
100
- // Sort Webmentions into groups by `wm-target` || `target`
101
- rawWebmentions.forEach((webmention) => {
102
- // Get the target of the Webmention and fix it up
103
- let url = baseUrl(fixUrl((webmention["wm-target"] || webmention["target"]).replace(/\/?$/, "/"), options.urlReplacements))
110
+ return webmentions
111
+ }
104
112
 
105
- if (!webmentions[url]) {
106
- webmentions[url] = []
107
- }
113
+ const filteredWebmentions = async (options) => {
114
+ const rawWebmentions = await fetchWebmentions(options)
115
+ let webmentions = {}
108
116
 
109
- webmentions[url].push(webmention)
110
- })
117
+ // Sort Webmentions into groups by target
118
+ rawWebmentions.forEach((webmention) => {
119
+ // Get the target of the Webmention and fix it up
120
+ let url = baseUrl(fixUrl(getTarget(webmention).replace(/\/?$/, "/"), options.urlReplacements))
111
121
 
112
- // Sort Webmentions in groups by url and remove duplicates by `url`
113
- for (let url in webmentions) {
114
- webmentions[url] = uniqBy(webmentions[url], (item) => {
115
- return item["url"]
116
- })
122
+ if (!webmentions[url]) {
123
+ webmentions[url] = []
117
124
  }
118
125
 
119
- return webmentions
126
+ webmentions[url].push(webmention)
127
+ })
128
+
129
+ // Sort Webmentions in groups by url and remove duplicates by `url`
130
+ for (let url in webmentions) {
131
+ webmentions[url] = uniqBy(webmentions[url], (entry) => {
132
+ return getSource(entry)
133
+ })
120
134
  }
121
135
 
122
- const getWebmentions = async (url, allowedTypes = {}) => {
123
- const webmentions = await filteredWebmentions()
124
- url = absoluteURL(url, options.domain)
136
+ return webmentions
137
+ }
125
138
 
126
- if (!url || !webmentions || !webmentions[url]) {
127
- return []
128
- }
139
+ const getWebmentions = async (options, url, allowedTypes = {}) => {
140
+ const webmentions = await filteredWebmentions(options)
141
+ url = absoluteURL(url, options.domain)
129
142
 
130
- const results = webmentions[url]
131
- // filter webmentions by allowed response post types
132
- .filter((entry) => {
133
- return typeof allowedTypes === "object" && Object.keys(allowedTypes).length ? allowedTypes.includes(entry["wm-property"] || entry["type"]) : true
134
- })
135
- // remove webmentions without an author name
136
- .filter((entry) => {
137
- const { author } = entry
138
- return !!author && !!author.name
139
- })
140
- // sanitize content of webmentions and check against HTML limit
141
- .map((entry) => {
142
- if (!("content" in entry)) {
143
- return entry
144
- }
145
- const { html, text } = entry.content
146
- if (html && html.length > options.maximumHtmlLength) {
147
- entry.content.value = `${options.maximumHtmlText} <a href="${entry["wm-source"] || entry["source"]}">${entry["wm-source"] || entry["source"]}</a>`
148
- } else if (Object.keys(options.allowedHTML).length) {
149
- entry.content.value = sanitizeHTML(html || text, options.allowedHTML)
150
- } else {
151
- entry.content.value = html || text
152
- }
143
+ if (!url || !webmentions || !webmentions[url]) {
144
+ return []
145
+ }
146
+
147
+ const results = webmentions[url]
148
+ // filter webmentions by allowed response post types
149
+ .filter((entry) => {
150
+ return typeof allowedTypes === "object" && Object.keys(allowedTypes).length ? allowedTypes.includes(getType(entry)) : true
151
+ })
152
+ // sanitize content of webmentions against HTML limit
153
+ .map((entry) => {
154
+ if (!("content" in entry) || !("data" in entry)) {
153
155
  return entry
154
- })
155
- // sort by `wm-received` || `published`
156
- .sort((a, b) => {
157
- return epoch(a["wm-received"] || a.published) - epoch(b["wm-received"] || b.published)
158
- })
156
+ }
157
+ const html = getContent(entry)
158
+ if (html && html.length > options.maximumHtmlLength) {
159
+ entry.content = `${options.maximumHtmlText} <a href="${getSource(entry)}">${getSource(entry)}</a>`
160
+ } else if (Object.keys(options.allowedHTML).length) {
161
+ entry.content = sanitizeHTML(html, options.allowedHTML)
162
+ } else {
163
+ entry.content = html
164
+ }
165
+ return entry
166
+ })
167
+ // sort by published
168
+ .sort((a, b) => {
169
+ return epoch(getPublished(a)) - epoch(getPublished(b))
170
+ })
159
171
 
160
- return results
172
+ return results
173
+ }
174
+
175
+ const getWebmentionsFilter = async (options, url, allowedTypes, callback) => {
176
+ if (typeof callback !== "function") {
177
+ callback = allowedTypes
178
+ allowedTypes = {}
161
179
  }
180
+ const webmentions = await getWebmentions(options, url, allowedTypes)
181
+ callback(null, webmentions)
182
+ }
162
183
 
163
- const getWebmentionsFilter = async (url, allowedTypes, callback) => {
164
- if (typeof callback !== "function") {
165
- callback = allowedTypes
166
- allowedTypes = {}
167
- }
168
- const webmentions = await getWebmentions(url, allowedTypes)
169
- callback(null, webmentions)
184
+ module.exports = (eleventyConfig, options = {}) => {
185
+ options = Object.assign(defaults, options)
186
+
187
+ if (!options.domain) {
188
+ throw new Error("domain is a required field when adding the plugin to your eleventyConfig using addPlugin. See https://chrisburnell.com/eleventy-cache-webmentions/#installation for more information.")
170
189
  }
171
190
 
172
- if (eleventyConfig && options) {
173
- if (!options.domain) {
174
- throw new Error("domain is a required field when adding the plugin to your eleventyConfig using addPlugin. See https://chrisburnell.com/eleventy-cache-webmentions/#installation for more information.")
175
- }
191
+ if (!options.feed) {
192
+ throw new Error("feed is a required field when adding the plugin to your eleventyConfig using addPlugin. See https://chrisburnell.com/eleventy-cache-webmentions/#installation for more information.")
193
+ }
176
194
 
177
- if (!options.feed) {
178
- throw new Error("feed is a required field when adding the plugin to your eleventyConfig using addPlugin. See https://chrisburnell.com/eleventy-cache-webmentions/#installation for more information.")
179
- }
195
+ if (!options.key) {
196
+ throw new Error("key is a required field when adding the plugin to your eleventyConfig using addPlugin. See https://chrisburnell.com/eleventy-cache-webmentions/#installation for more information.")
197
+ }
180
198
 
181
- if (!options.key) {
182
- throw new Error("key is a required field when adding the plugin to your eleventyConfig using addPlugin. See https://chrisburnell.com/eleventy-cache-webmentions/#installation for more information.")
183
- }
199
+ if (eleventyConfig) {
200
+ // Global Data
201
+ eleventyConfig.addGlobalData("webmentions", filteredWebmentions(options))
184
202
 
185
203
  // Liquid Filter
186
204
  eleventyConfig.addLiquidFilter("getWebmentions", getWebmentionsFilter)
187
205
 
188
206
  // Nunjucks Filter
189
207
  eleventyConfig.addNunjucksAsyncFilter("getWebmentions", getWebmentionsFilter)
208
+ }
190
209
 
191
- // Global Data
192
- eleventyConfig.addGlobalData("webmentions", filteredWebmentions)
193
- } else {
194
- // JavaScript
195
- return filteredWebmentions
210
+ return {
211
+ defaults: defaults,
212
+ webmentions: filteredWebmentions(options),
213
+ fetchWebmentions: fetchWebmentions,
214
+ filteredWebmentions: filteredWebmentions,
215
+ getWebmentions: getWebmentions,
196
216
  }
197
217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrisburnell/eleventy-cache-webmentions",
3
- "version": "0.2.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Fetch and cache webmentions using eleventy-fetch.",
5
5
  "main": "eleventy-cache-webmentions.js",
6
6
  "author": "Chris Burnell <me@chrisburnell.com>",