@chrisburnell/eleventy-cache-webmentions 0.2.1 → 1.0.0-beta.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.
- package/eleventy-cache-webmentions.js +121 -119
- package/package.json +1 -1
|
@@ -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 {
|
|
@@ -57,156 +58,157 @@ const getType = (webmention) => {
|
|
|
57
58
|
return webmention["wm-property"] || webmention?.["activity"]["type"] || webmention["type"]
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
allowedAttributes: {
|
|
67
|
-
a: ["href"],
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
urlReplacements: {},
|
|
71
|
-
maximumHtmlLength: 2000,
|
|
72
|
-
maximumHtmlText: "mentioned this in",
|
|
61
|
+
const defaults = {
|
|
62
|
+
uniqueKey: "webmentions",
|
|
63
|
+
allowedHTML: {
|
|
64
|
+
allowedTags: ["b", "i", "em", "strong", "a"],
|
|
65
|
+
allowedAttributes: {
|
|
66
|
+
a: ["href"],
|
|
73
67
|
},
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
},
|
|
69
|
+
urlReplacements: {},
|
|
70
|
+
maximumHtmlLength: 2000,
|
|
71
|
+
maximumHtmlText: "mentioned this in",
|
|
72
|
+
}
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
const fetchWebmentions = async (options) => {
|
|
75
|
+
let asset = new AssetCache(options.uniqueKey)
|
|
76
|
+
asset.ensureDir()
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
let webmentions = []
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
// If there is a cached file at all, grab its contents now
|
|
81
|
+
if (asset.isCacheValid("9001y")) {
|
|
82
|
+
webmentions = await asset.getCachedValue()
|
|
83
|
+
}
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
await asset.save(webmentions, "json")
|
|
104
|
-
return 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.`)
|
|
105
99
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
})
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
let webmentions = {}
|
|
119
|
-
|
|
120
|
-
// Sort Webmentions into groups by target
|
|
121
|
-
rawWebmentions.forEach((webmention) => {
|
|
122
|
-
// Get the target of the Webmention and fix it up
|
|
123
|
-
let url = baseUrl(fixUrl(getTarget(webmention).replace(/\/?$/, "/"), options.urlReplacements))
|
|
110
|
+
return webmentions
|
|
111
|
+
}
|
|
124
112
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
113
|
+
const filteredWebmentions = async (options) => {
|
|
114
|
+
const rawWebmentions = await fetchWebmentions(options)
|
|
115
|
+
let webmentions = {}
|
|
128
116
|
|
|
129
|
-
|
|
130
|
-
|
|
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))
|
|
131
121
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
webmentions[url] = uniqBy(webmentions[url], (entry) => {
|
|
135
|
-
return getSource(entry)
|
|
136
|
-
})
|
|
122
|
+
if (!webmentions[url]) {
|
|
123
|
+
webmentions[url] = []
|
|
137
124
|
}
|
|
138
125
|
|
|
139
|
-
|
|
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
|
+
})
|
|
140
134
|
}
|
|
141
135
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
url = absoluteURL(url, options.domain)
|
|
136
|
+
return webmentions
|
|
137
|
+
}
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
139
|
+
const getWebmentions = async (options, url, allowedTypes = {}) => {
|
|
140
|
+
const webmentions = await filteredWebmentions(options)
|
|
141
|
+
url = absoluteURL(url, options.domain)
|
|
149
142
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
entry.content = `${options.maximumHtmlText} <a href="${getSource(entry)}">${getSource(entry)}</a>`
|
|
163
|
-
} else if (Object.keys(options.allowedHTML).length) {
|
|
164
|
-
entry.content = sanitizeHTML(html, options.allowedHTML)
|
|
165
|
-
} else {
|
|
166
|
-
entry.content = html
|
|
167
|
-
}
|
|
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)) {
|
|
168
155
|
return entry
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
})
|
|
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
|
+
})
|
|
174
171
|
|
|
175
|
-
|
|
172
|
+
return results
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const getWebmentionsFilter = async (options, url, allowedTypes, callback) => {
|
|
176
|
+
if (typeof callback !== "function") {
|
|
177
|
+
callback = allowedTypes
|
|
178
|
+
allowedTypes = {}
|
|
176
179
|
}
|
|
180
|
+
const webmentions = await getWebmentions(options, url, allowedTypes)
|
|
181
|
+
callback(null, webmentions)
|
|
182
|
+
}
|
|
177
183
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const webmentions = await getWebmentions(url, allowedTypes)
|
|
184
|
-
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.")
|
|
185
189
|
}
|
|
186
190
|
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
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
|
+
}
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
}
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
if (eleventyConfig) {
|
|
200
|
+
// Global Data
|
|
201
|
+
eleventyConfig.addGlobalData("webmentions", filteredWebmentions(options))
|
|
199
202
|
|
|
200
203
|
// Liquid Filter
|
|
201
204
|
eleventyConfig.addLiquidFilter("getWebmentions", getWebmentionsFilter)
|
|
202
205
|
|
|
203
206
|
// Nunjucks Filter
|
|
204
207
|
eleventyConfig.addNunjucksAsyncFilter("getWebmentions", getWebmentionsFilter)
|
|
208
|
+
}
|
|
205
209
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// JavaScript
|
|
210
|
-
return filteredWebmentions
|
|
210
|
+
return {
|
|
211
|
+
webmentions: filteredWebmentions(options),
|
|
212
|
+
getWebmentions: getWebmentions,
|
|
211
213
|
}
|
|
212
214
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrisburnell/eleventy-cache-webmentions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
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>",
|