@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.
@@ -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
- module.exports = (eleventyConfig, options = {}) => {
46
- options = Object.assign(
47
- {
48
- key: "webmentions",
49
- allowedHTML: {
50
- allowedTags: ["b", "i", "em", "strong", "a"],
51
- allowedAttributes: {
52
- a: ["href"],
53
- },
54
- },
55
- urlReplacements: {},
56
- maximumHtmlLength: 2000,
57
- maximumHtmlText: "mentioned this in",
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
- options
60
- )
68
+ },
69
+ urlReplacements: {},
70
+ maximumHtmlLength: 2000,
71
+ maximumHtmlText: "mentioned this in",
72
+ }
61
73
 
62
- const fetchWebmentions = async () => {
63
- let asset = new AssetCache(options.uniqueKey || options.key)
64
- asset.ensureDir()
74
+ const fetchWebmentions = async (options) => {
75
+ let asset = new AssetCache(options.uniqueKey)
76
+ asset.ensureDir()
65
77
 
66
- let webmentions = {
67
- type: "feed",
68
- name: "Webmentions",
69
- children: [],
70
- }
78
+ let webmentions = []
71
79
 
72
- // If there is a cached file at all, grab its contents now
73
- if (asset.isCacheValid("9001y")) {
74
- webmentions = await asset.getCachedValue()
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
- // If there is a cached file but it is outside of expiry, fetch fresh
78
- // results since the most recent
79
- if (!asset.isCacheValid(options.duration)) {
80
- const since = webmentions.children.length ? webmentions.children[0]["wm-received"] : false
81
- const url = `https://webmention.io/api/mentions.jf2?domain=${hostname(options.domain)}&token=${TOKEN}&per-page=9001${since ? `&since=${since}` : ``}`
82
- await fetch(url)
83
- .then(async (response) => {
84
- if (response.ok) {
85
- const feed = await response.json()
86
- if (feed.children.length) {
87
- console.log(`[${hostname(options.domain)}] ${feed.children.length} new Webmentions fetched into cache.`)
88
- }
89
- webmentions.children = [...feed.children, ...webmentions.children]
90
- await asset.save(webmentions, "json")
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
- return Promise.reject(response)
94
- })
95
- .catch((error) => {
96
- console.log(`[${hostname(options.domain)}] Something went wrong with your request to webmention.io!`, error)
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
- 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
+ })
101
134
  }
102
135
 
103
- const filteredWebmentions = async () => {
104
- const rawWebmentions = await fetchWebmentions()
105
- let webmentions = {}
136
+ return webmentions
137
+ }
106
138
 
107
- // Sort Webmentions into groups by `wm-target`
108
- rawWebmentions.children.forEach((webmention) => {
109
- // Get the target of the Webmention and fix it up
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
- if (!webmentions[url]) {
113
- webmentions[url] = []
114
- }
143
+ if (!url || !webmentions || !webmentions[url]) {
144
+ return []
145
+ }
115
146
 
116
- webmentions[url].push(webmention)
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
- // Sort Webmentions in groups by url and remove duplicates by `url`
120
- for (let url in webmentions) {
121
- webmentions[url] = uniqBy(webmentions[url], (item) => {
122
- return item["url"]
123
- })
124
- }
172
+ return results
173
+ }
125
174
 
126
- return webmentions
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
- const getWebmentions = async (url, allowedTypes = {}) => {
130
- const webmentions = await filteredWebmentions()
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
- const results = webmentions[url]
138
- // filter webmentions by allowed response post types
139
- .filter((entry) => {
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
- return results
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
- const getWebmentionsFilter = async (url, allowedTypes, callback) => {
171
- if (typeof callback !== "function") {
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 && options) {
180
- if (!options.domain) {
181
- throw new Error("domain is a required option to be passed when adding the plugin to your eleventyConfig using addPlugin.")
182
- }
199
+ if (eleventyConfig) {
200
+ // Global Data
201
+ eleventyConfig.addGlobalData("webmentions", filteredWebmentions(options))
183
202
 
184
- // Liquid/Nunjucks Filters
203
+ // Liquid Filter
185
204
  eleventyConfig.addLiquidFilter("getWebmentions", getWebmentionsFilter)
205
+
206
+ // Nunjucks Filter
186
207
  eleventyConfig.addNunjucksAsyncFilter("getWebmentions", getWebmentionsFilter)
208
+ }
187
209
 
188
- // Eleventy Data
189
- eleventyConfig.addGlobalData("webmentions", filteredWebmentions)
190
- } else {
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.12",
4
- "description": "Cache webmentions from webmention.io using eleventy-fetch.",
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"