@chrisburnell/eleventy-cache-webmentions 0.1.12 → 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 +145 -125
- package/package.json +2 -3
|
@@ -2,11 +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
|
-
|
|
6
|
-
// Load .env variables with dotenv
|
|
7
|
-
require("dotenv").config()
|
|
8
|
-
|
|
9
|
-
const TOKEN = process.env.WEBMENTION_IO_TOKEN
|
|
5
|
+
const { defaultsDeep } = require("lodash")
|
|
10
6
|
|
|
11
7
|
const absoluteURL = (url, domain) => {
|
|
12
8
|
try {
|
|
@@ -42,153 +38,177 @@ const epoch = (value) => {
|
|
|
42
38
|
return new Date(value).getTime()
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
const getPublished = (webmention) => {
|
|
42
|
+
return webmention["wm-received"] || webmention?.["data"]["published"] || webmention["verified_date"] || webmention["published"]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getContent = (webmention) => {
|
|
46
|
+
return webmention?.["content"]["html"] || webmention["content"] || webmention?.["data"]["content"]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getSource = (webmention) => {
|
|
50
|
+
return webmention["url"] || webmention?.["data"]["url"] || webmention["wm-source"] || webmention["source"]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const getTarget = (webmention) => {
|
|
54
|
+
return webmention["wm-target"] || webmention["target"]
|
|
55
|
+
}
|
|
56
|
+
|
|
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"],
|
|
58
67
|
},
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
},
|
|
69
|
+
urlReplacements: {},
|
|
70
|
+
maximumHtmlLength: 2000,
|
|
71
|
+
maximumHtmlText: "mentioned this in",
|
|
72
|
+
}
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
const fetchWebmentions = async (options) => {
|
|
75
|
+
let asset = new AssetCache(options.uniqueKey)
|
|
76
|
+
asset.ensureDir()
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
type: "feed",
|
|
68
|
-
name: "Webmentions",
|
|
69
|
-
children: [],
|
|
70
|
-
}
|
|
78
|
+
let webmentions = []
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
// If there is a cached file at all, grab its contents now
|
|
81
|
+
if (asset.isCacheValid("9001y")) {
|
|
82
|
+
webmentions = await asset.getCachedValue()
|
|
83
|
+
}
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
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.`)
|
|
92
99
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
}
|
|
109
|
+
|
|
110
|
+
return webmentions
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const filteredWebmentions = async (options) => {
|
|
114
|
+
const rawWebmentions = await fetchWebmentions(options)
|
|
115
|
+
let webmentions = {}
|
|
116
|
+
|
|
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))
|
|
121
|
+
|
|
122
|
+
if (!webmentions[url]) {
|
|
123
|
+
webmentions[url] = []
|
|
98
124
|
}
|
|
99
125
|
|
|
100
|
-
|
|
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
|
+
})
|
|
101
134
|
}
|
|
102
135
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
let webmentions = {}
|
|
136
|
+
return webmentions
|
|
137
|
+
}
|
|
106
138
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let url = baseUrl(fixUrl(webmention["wm-target"].replace(/\/?$/, "/"), options.urlReplacements))
|
|
139
|
+
const getWebmentions = async (options, url, allowedTypes = {}) => {
|
|
140
|
+
const webmentions = await filteredWebmentions(options)
|
|
141
|
+
url = absoluteURL(url, options.domain)
|
|
111
142
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
143
|
+
if (!url || !webmentions || !webmentions[url]) {
|
|
144
|
+
return []
|
|
145
|
+
}
|
|
115
146
|
|
|
116
|
-
|
|
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)) {
|
|
155
|
+
return entry
|
|
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))
|
|
117
170
|
})
|
|
118
171
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
webmentions[url] = uniqBy(webmentions[url], (item) => {
|
|
122
|
-
return item["url"]
|
|
123
|
-
})
|
|
124
|
-
}
|
|
172
|
+
return results
|
|
173
|
+
}
|
|
125
174
|
|
|
126
|
-
|
|
175
|
+
const getWebmentionsFilter = async (options, url, allowedTypes, callback) => {
|
|
176
|
+
if (typeof callback !== "function") {
|
|
177
|
+
callback = allowedTypes
|
|
178
|
+
allowedTypes = {}
|
|
127
179
|
}
|
|
180
|
+
const webmentions = await getWebmentions(options, url, allowedTypes)
|
|
181
|
+
callback(null, webmentions)
|
|
182
|
+
}
|
|
128
183
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
url = absoluteURL(url, options.domain)
|
|
132
|
-
|
|
133
|
-
if (!url || !webmentions || !webmentions[url]) {
|
|
134
|
-
return []
|
|
135
|
-
}
|
|
184
|
+
module.exports = (eleventyConfig, options = {}) => {
|
|
185
|
+
options = Object.assign(defaults, options)
|
|
136
186
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return typeof allowedTypes === "object" && Object.keys(allowedTypes).length ? allowedTypes.includes(entry["wm-property"]) : true
|
|
141
|
-
})
|
|
142
|
-
// remove webmentions without an author name
|
|
143
|
-
.filter((entry) => {
|
|
144
|
-
const { author } = entry
|
|
145
|
-
return !!author && !!author.name
|
|
146
|
-
})
|
|
147
|
-
// sanitize content of webmentions and check against HTML limit
|
|
148
|
-
.map((entry) => {
|
|
149
|
-
if (!("content" in entry)) {
|
|
150
|
-
return entry
|
|
151
|
-
}
|
|
152
|
-
const { html, text } = entry.content
|
|
153
|
-
if (html && html.length > options.maximumHtmlLength) {
|
|
154
|
-
entry.content.value = `${options.maximumHtmlText} <a href="${entry["wm-source"]}">${entry["wm-source"]}</a>`
|
|
155
|
-
} else if (Object.keys(options.allowedHTML).length) {
|
|
156
|
-
entry.content.value = sanitizeHTML(html || text, options.allowedHTML)
|
|
157
|
-
} else {
|
|
158
|
-
entry.content.value = html || text
|
|
159
|
-
}
|
|
160
|
-
return entry
|
|
161
|
-
})
|
|
162
|
-
// sort by published/wm-received
|
|
163
|
-
.sort((a, b) => {
|
|
164
|
-
return epoch(a.published || a["wm-received"]) - epoch(b.published || b["wm-received"])
|
|
165
|
-
})
|
|
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.")
|
|
189
|
+
}
|
|
166
190
|
|
|
167
|
-
|
|
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.")
|
|
168
193
|
}
|
|
169
194
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
callback = allowedTypes
|
|
173
|
-
allowedTypes = {}
|
|
174
|
-
}
|
|
175
|
-
const webmentions = await getWebmentions(url, allowedTypes)
|
|
176
|
-
callback(null, webmentions)
|
|
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.")
|
|
177
197
|
}
|
|
178
198
|
|
|
179
|
-
if (eleventyConfig
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
199
|
+
if (eleventyConfig) {
|
|
200
|
+
// Global Data
|
|
201
|
+
eleventyConfig.addGlobalData("webmentions", filteredWebmentions(options))
|
|
183
202
|
|
|
184
|
-
// Liquid
|
|
203
|
+
// Liquid Filter
|
|
185
204
|
eleventyConfig.addLiquidFilter("getWebmentions", getWebmentionsFilter)
|
|
205
|
+
|
|
206
|
+
// Nunjucks Filter
|
|
186
207
|
eleventyConfig.addNunjucksAsyncFilter("getWebmentions", getWebmentionsFilter)
|
|
208
|
+
}
|
|
187
209
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// JavaScript
|
|
192
|
-
return filteredWebmentions
|
|
210
|
+
return {
|
|
211
|
+
webmentions: filteredWebmentions(options),
|
|
212
|
+
getWebmentions: getWebmentions,
|
|
193
213
|
}
|
|
194
214
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrisburnell/eleventy-cache-webmentions",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Fetch and cache webmentions using eleventy-fetch.",
|
|
5
5
|
"main": "eleventy-cache-webmentions.js",
|
|
6
6
|
"author": "Chris Burnell <me@chrisburnell.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@11ty/eleventy-fetch": "^3.0.0",
|
|
20
|
-
"dotenv": "^10.0.0",
|
|
21
20
|
"lodash": "^4.17.21",
|
|
22
21
|
"node-fetch": "2.6.5",
|
|
23
22
|
"sanitize-html": "^2.5.2"
|