├── .eleventy.js ├── .gitignore ├── .nvmrc ├── .prettierrc ├── README.md ├── filters.js ├── package-lock.json ├── package.json └── webmentions.js /.eleventy.js: -------------------------------------------------------------------------------- 1 | const Webmentions = require("./webmentions"); 2 | const WebmentionFilters = require("./filters"); 3 | 4 | const config = async (eleventyConfig, options = {}) => { 5 | const webmentions = Webmentions(options); 6 | const filters = WebmentionFilters(options); 7 | 8 | const data = webmentions.get(); 9 | 10 | eleventyConfig.addGlobalData("webmentions", async () => { 11 | const { children } = await data; 12 | return children; 13 | }); 14 | 15 | eleventyConfig.addGlobalData("webmentionsLastFetched", async () => { 16 | const { lastFetched } = await data; 17 | return new Date(lastFetched); 18 | }); 19 | 20 | eleventyConfig.addFilter("webmentionsForPage", filters.mentions); 21 | eleventyConfig.addFilter("webmentionCountForPage", filters.count); 22 | }; 23 | 24 | config.defaults = { 25 | ...Webmentions.defaults, 26 | ...WebmentionFilters.defaults, 27 | }; 28 | 29 | module.exports = config; 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.13.0 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eleventy-plugin-webmentions 2 | 3 | A plugin for eleventy to fetch and filter [webmentions](https://indieweb.org/Webmention) from [Webmention.io](https://webmention.io). 4 | 5 | ## Install 6 | 7 | Available on [npm](https://www.npmjs.com/package/eleventy-plugin-webmentions). 8 | 9 | `npm install --save-dev eleventy-plugin-webmentions` 10 | 11 | ## Usage 12 | 13 | In your Eleventy config file (probably `.eleventy.js`), load the plugin module and use `.addPlugin` to add it to Eleventy with an options object that defines the `domain` and the Webmention.io `token`. Like this: 14 | 15 | ```javascript 16 | const Webmentions = require("eleventy-plugin-webmentions"); 17 | 18 | module.exports = function (eleventyConfig) { 19 | eleventyConfig.addPlugin(Webmentions, { 20 | domain: "lukeb.co.uk", 21 | token: "ABC123XYZ987", 22 | }); 23 | }; 24 | ``` 25 | 26 | REMEMBER: You’re only allowed one `module.exports` in your configuration file, so make sure you only copy the `require` and the `.addPlugin` lines above! (Including the configuration options) 27 | 28 | The plugin then adds 2 global data objects. One is called `webmentionsLastFetched` and is a `Date` object with the date that the plugin last fetched webmentions, and the other is called `webmentions` and is an array of webmention objects that look similar to this: 29 | 30 | ```javascript 31 | { 32 | type: 'entry', 33 | author: { 34 | type: 'card', 35 | name: 'Zach Leatherman', 36 | photo: 'https://webmention.io/avatar/pbs.twimg.com/d9711a9ad30ae05a761e4a728883bcbdd852cbf7d41437925b0afc47a8589795.jpg', 37 | url: 'https://twitter.com/zachleat' 38 | }, 39 | url: 'https://twitter.com/zachleat/status/1524800520208142337', 40 | published: '2022-05-12T17:15:48+00:00', 41 | 'wm-received': '2022-05-13T00:05:16Z', 42 | 'wm-id': 1397424, 43 | 'wm-source': 'https://brid.gy/comment/twitter/CodeFoodPixels/1524795680966991874/1524800520208142337', 44 | 'wm-target': 'https://lukeb.co.uk/blog/2022/01/17/pixelated-rounded-corners-with-css-clip-path/', 45 | content: { 46 | html: 'The step-by-step here was/is incredible detailed!\n' + 47 | '\n' + 48 | '', 49 | text: 'The step-by-step here was/is incredible detailed!', 50 | value: 'The step-by-step here was/is incredible detailed! ' 51 | }, 52 | 'in-reply-to': 'https://lukeb.co.uk/blog/2022/01/17/pixelated-rounded-corners-with-css-clip-path/', 53 | 'wm-property': 'in-reply-to', 54 | 'wm-private': false 55 | } 56 | ``` 57 | 58 | It also adds 2 filters: 59 | 60 | - `webmentionsForPage` will return the webmentions for that page, in the structure defined by the `mentionTypes` option. 61 | - `webmentionCountForPage` will return the number of webmentions for a page, filtered by the types used in the `mentionTypes` option. 62 | 63 | Here is an example of using the filters in nunjucks: 64 | 65 | ```nunjucks 66 | {# Get the webmentions for the current page #} 67 | {%- set currentPostMentions = webmentions | webmentionsForPage -%} 68 | 69 | {# Get the webmentions for a specific page #} 70 | {%- set postMentions = webmentions | webmentionsForPage(post.url) -%} 71 | 72 | {# Get the webmention count for the current page #} 73 | {%- set currentPostMentionCount = webmentions | webmentionCountForPage -%} 74 | 75 | {# Get the webmention count for a page #} 76 | {%- set postMentionCount = webmentions | webmentionCountForPage(post.url) -%} 77 | 78 | ``` 79 | 80 | ## Configuration 81 | 82 | Below are all the options that can be passed to the plugin: 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 100 | 101 | 106 | 111 | 112 | 113 | 114 | 115 | 120 | 121 | 126 | 131 | 132 | 133 | 134 | 135 | 140 | 141 | 142 | 147 | 148 | 149 | 150 | 151 | 156 | 157 | 158 | 163 | 164 | 165 | 166 | 167 | 172 | 173 | 174 | 179 | 180 | 181 | 182 | 183 | 188 | 189 | 190 | 195 | 196 | 197 | 198 | 199 | 204 | 205 | 206 | 211 | 212 | 213 | 214 | 215 | 220 | 221 | 222 | 227 | 228 | 229 | 230 | 231 | 236 | 237 | 238 | 243 | 248 | 249 | 250 | 251 | 256 | 257 | 258 | 263 | 268 | 269 | 270 | 271 | 276 | 277 | 278 | 292 | 297 | 298 | 299 | 300 | 305 | 306 | 307 | 319 | 324 | 325 | 326 | 327 | 332 | 333 | 334 | 343 | 344 | 345 | 346 |
OptionTypeRequired?DefaultDescription
96 | 97 | `domain` 98 | 99 | string 102 | 103 | **Required** 104 | 105 | 107 | 108 | `undefined` 109 | 110 | The domain you wish to get the webmentions for.
116 | 117 | `token` 118 | 119 | string 122 | 123 | **Required** 124 | 125 | 127 | 128 | `undefined` 129 | 130 | The webmention.io token (found at the bottom of [the webmention.io settings page](https://webmention.io/settings)).
136 | 137 | `cacheDirectory` 138 | 139 | stringOptional 143 | 144 | `./_webmentioncache` 145 | 146 | The directory for webmentions to be cached to.
152 | 153 | `cacheTime` 154 | 155 | integerOptional 159 | 160 | `3600` 161 | 162 | The time in seconds for the cached webmentions to be considered "fresh".
168 | 169 | `truncate` 170 | 171 | booleanOptional 175 | 176 | `true` 177 | 178 | Whether or not to truncate the webmentions
184 | 185 | `maxContentLength` 186 | 187 | integerOptional 191 | 192 | `280` 193 | 194 | The length to truncate webmentions to if `truncate` is true
200 | 201 | `truncationMarker` 202 | 203 | stringOptional 207 | 208 | `…` 209 | 210 | The string to truncate the content with
216 | 217 | `htmlContent` 218 | 219 | booleanOptional 223 | 224 | `true` 225 | 226 | Whether or not to return HTML content from the webmentions. If `false`, just text content will be returned.
232 | 233 | `useCanonicalTwitterUrls` 234 | 235 | booleanOptional 239 | 240 | `true` 241 | 242 | 244 | 245 | Whether or not to convert Twitter URLs using [tweetback-canonical](https://github.com/tweetback/tweetback-canonical) 246 | 247 |
252 | 253 | `pageAliases` 254 | 255 | objectOptional 259 | 260 | `{}` 261 | 262 | 264 | 265 | An object keyed by page path, with the values either being a string of a page that is an alias of that page (e.g an old page that has been redirected) or an array of strings. 266 | 267 |
272 | 273 | `mentionTypes` 274 | 275 | objectOptional 279 | 280 | ```javascript 281 | { 282 | likes: ["like-of"], 283 | reposts: ["repost-of"], 284 | comments: [ 285 | "mention-of", 286 | "in-reply-to" 287 | ] 288 | } 289 | ``` 290 | 291 | 293 | 294 | A single layer object with groupings and types that should be returned for that grouping. The object can have any keys you wish (doesn't have to be `likes`, `reposts` and `comments` like the default) but each value should be an array of webmention types.[You can find a list of possible types here](https://github.com/aaronpk/webmention.io#find-links-of-a-specific-type-to-a-specific-page) 295 | 296 |
301 | 302 | `sanitizeOptions` 303 | 304 | objectOptional 308 | 309 | ```javascript 310 | { 311 | allowedTags: ["b", "i", "em", "strong", "a", "p"], 312 | allowedAttributes: { 313 | a: ["href"], 314 | }, 315 | } 316 | ``` 317 | 318 | 320 | 321 | A set of options passed to `sanitize-html`. You can find a full list of available options here [You can find a full list of available options here](https://github.com/apostrophecms/sanitize-html) 322 | 323 |
328 | 329 | `sortFunction` 330 | 331 | functionOptional 335 | 336 | ```javascript 337 | (a, b) => { 338 | new Date(a.published || a["wm-received"]) - 339 | new Date(b.published || b["wm-received"]) 340 | ``` 341 | 342 | A function to use when sorting the webmentions. By default, the webmentions will be sorted in date ascending order, either by when they were published or when they were recieved.
347 | 348 | ### Defaults 349 | 350 | All of the defaults are exposed on the `defaults` property of the module, so they can be used in your config if necessary. 351 | 352 | Here is an example of extending the `sanitizeOptions` object: 353 | 354 | ```javascript 355 | const Webmentions = require("eleventy-plugin-webmentions"); 356 | 357 | module.exports = function (eleventyConfig) { 358 | eleventyConfig.addPlugin(Webmentions, { 359 | domain: "lukeb.co.uk", 360 | token: "ABC123XYZ987", 361 | sanitizeOptions: { 362 | ...Webmentions.defaults.sanitizeOptions, 363 | allowedTags: [ 364 | ...Webmentions.defaults.sanitizeOptions.allowedTags, 365 | "iframe", 366 | "marquee", 367 | ], 368 | disallowedTagsMode: "escape", 369 | }, 370 | }); 371 | }; 372 | ``` 373 | -------------------------------------------------------------------------------- /filters.js: -------------------------------------------------------------------------------- 1 | const { URL } = require("url"); 2 | 3 | const defaults = { 4 | mentionTypes: { 5 | likes: ["like-of"], 6 | reposts: ["repost-of"], 7 | comments: ["mention-of", "in-reply-to"], 8 | }, 9 | }; 10 | 11 | function stripOuterSlashes(str) { 12 | let start = 0; 13 | while (str[start++] === "/"); 14 | let end = str.length; 15 | while (str[--end] === "/"); 16 | return str.slice(start - 1, end + 1); 17 | } 18 | 19 | const filters = ({ 20 | mentionTypes = defaults.mentionTypes, 21 | pageAliases = {}, 22 | }) => { 23 | const cleanedAliases = Object.keys(pageAliases).reduce((cleaned, key) => { 24 | cleaned[stripOuterSlashes(key.toLowerCase())] = 25 | typeof pageAliases[key] === "string" 26 | ? [stripOuterSlashes(pageAliases[key].toLowerCase())] 27 | : pageAliases[key].map((alias) => 28 | stripOuterSlashes(alias.toLowerCase()) 29 | ); 30 | 31 | return cleaned; 32 | }, {}); 33 | 34 | function filterWebmentions(webmentions, page) { 35 | const pageUrl = new URL(page, "https://lukeb.co.uk"); 36 | const normalizedPagePath = stripOuterSlashes( 37 | pageUrl.pathname.toLowerCase() 38 | ); 39 | 40 | const flattenedMentionTypes = Object.values(mentionTypes).flat(); 41 | 42 | return webmentions 43 | .filter((mention) => { 44 | const target = new URL(mention["wm-target"]); 45 | const normalisedTargetPath = stripOuterSlashes( 46 | target.pathname.toLowerCase() 47 | ); 48 | return ( 49 | normalizedPagePath === normalisedTargetPath || 50 | cleanedAliases[normalizedPagePath]?.includes(normalisedTargetPath) 51 | ); 52 | }) 53 | .filter( 54 | (entry) => !!entry.author && (!!entry.author.name || entry.author.url) 55 | ) 56 | .filter((mention) => 57 | flattenedMentionTypes.includes(mention["wm-property"]) 58 | ); 59 | } 60 | 61 | function count(webmentions, pageUrl) { 62 | const page = 63 | pageUrl || 64 | this.page?.url || 65 | this.ctx?.page?.url || 66 | this.context?.environments?.page?.url; 67 | 68 | return filterWebmentions(webmentions, page).length; 69 | } 70 | 71 | function mentions(webmentions, pageUrl) { 72 | const page = 73 | pageUrl || 74 | this.page?.url || 75 | this.ctx?.page?.url || 76 | this.context?.environments?.page?.url; 77 | 78 | const filteredWebmentions = filterWebmentions(webmentions, page); 79 | 80 | const returnedWebmentions = { 81 | total: filteredWebmentions.length, 82 | }; 83 | 84 | Object.keys(mentionTypes).map((type) => { 85 | returnedWebmentions[type] = filteredWebmentions.filter((mention) => 86 | mentionTypes[type].includes(mention["wm-property"]) 87 | ); 88 | }); 89 | 90 | return returnedWebmentions; 91 | } 92 | 93 | return { 94 | count, 95 | mentions, 96 | }; 97 | }; 98 | 99 | filters.defaults = defaults; 100 | 101 | module.exports = filters; 102 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-plugin-webmentions", 3 | "version": "2.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "eleventy-plugin-webmentions", 9 | "version": "2.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@tweetback/canonical": "^2.0.27", 13 | "html-entities": "^2.3.3", 14 | "node-fetch": "^2.6.6", 15 | "sanitize-html": "^2.7.0", 16 | "truncate-html": "^1.0.4" 17 | } 18 | }, 19 | "node_modules/@tweetback/canonical": { 20 | "version": "2.0.27", 21 | "resolved": "https://registry.npmjs.org/@tweetback/canonical/-/canonical-2.0.27.tgz", 22 | "integrity": "sha512-kfSBGynrL/nDBp0oQyCXo5geZi2+q9A63IrmtKaVHCmPMhFs6U8USnnFB3Ldft7XKEyDjM/EGFB7SOnT09OwqA==" 23 | }, 24 | "node_modules/@types/cheerio": { 25 | "version": "0.22.31", 26 | "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", 27 | "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==", 28 | "dependencies": { 29 | "@types/node": "*" 30 | } 31 | }, 32 | "node_modules/@types/node": { 33 | "version": "18.0.0", 34 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", 35 | "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==" 36 | }, 37 | "node_modules/boolbase": { 38 | "version": "1.0.0", 39 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 40 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" 41 | }, 42 | "node_modules/cheerio": { 43 | "version": "0.22.0", 44 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", 45 | "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", 46 | "dependencies": { 47 | "css-select": "~1.2.0", 48 | "dom-serializer": "~0.1.0", 49 | "entities": "~1.1.1", 50 | "htmlparser2": "^3.9.1", 51 | "lodash.assignin": "^4.0.9", 52 | "lodash.bind": "^4.1.4", 53 | "lodash.defaults": "^4.0.1", 54 | "lodash.filter": "^4.4.0", 55 | "lodash.flatten": "^4.2.0", 56 | "lodash.foreach": "^4.3.0", 57 | "lodash.map": "^4.4.0", 58 | "lodash.merge": "^4.4.0", 59 | "lodash.pick": "^4.2.1", 60 | "lodash.reduce": "^4.4.0", 61 | "lodash.reject": "^4.4.0", 62 | "lodash.some": "^4.4.0" 63 | }, 64 | "engines": { 65 | "node": ">= 0.6" 66 | } 67 | }, 68 | "node_modules/cheerio/node_modules/dom-serializer": { 69 | "version": "0.1.1", 70 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", 71 | "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", 72 | "dependencies": { 73 | "domelementtype": "^1.3.0", 74 | "entities": "^1.1.1" 75 | } 76 | }, 77 | "node_modules/cheerio/node_modules/domelementtype": { 78 | "version": "1.3.1", 79 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 80 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 81 | }, 82 | "node_modules/cheerio/node_modules/domhandler": { 83 | "version": "2.4.2", 84 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", 85 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", 86 | "dependencies": { 87 | "domelementtype": "1" 88 | } 89 | }, 90 | "node_modules/cheerio/node_modules/domutils": { 91 | "version": "1.7.0", 92 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", 93 | "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", 94 | "dependencies": { 95 | "dom-serializer": "0", 96 | "domelementtype": "1" 97 | } 98 | }, 99 | "node_modules/cheerio/node_modules/entities": { 100 | "version": "1.1.2", 101 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", 102 | "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" 103 | }, 104 | "node_modules/cheerio/node_modules/htmlparser2": { 105 | "version": "3.10.1", 106 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", 107 | "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", 108 | "dependencies": { 109 | "domelementtype": "^1.3.1", 110 | "domhandler": "^2.3.0", 111 | "domutils": "^1.5.1", 112 | "entities": "^1.1.1", 113 | "inherits": "^2.0.1", 114 | "readable-stream": "^3.1.1" 115 | } 116 | }, 117 | "node_modules/css-select": { 118 | "version": "1.2.0", 119 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 120 | "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", 121 | "dependencies": { 122 | "boolbase": "~1.0.0", 123 | "css-what": "2.1", 124 | "domutils": "1.5.1", 125 | "nth-check": "~1.0.1" 126 | } 127 | }, 128 | "node_modules/css-select/node_modules/dom-serializer": { 129 | "version": "0.2.2", 130 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", 131 | "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", 132 | "dependencies": { 133 | "domelementtype": "^2.0.1", 134 | "entities": "^2.0.0" 135 | } 136 | }, 137 | "node_modules/css-select/node_modules/domutils": { 138 | "version": "1.5.1", 139 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 140 | "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", 141 | "dependencies": { 142 | "dom-serializer": "0", 143 | "domelementtype": "1" 144 | } 145 | }, 146 | "node_modules/css-select/node_modules/domutils/node_modules/domelementtype": { 147 | "version": "1.3.1", 148 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 149 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 150 | }, 151 | "node_modules/css-what": { 152 | "version": "2.1.3", 153 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", 154 | "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", 155 | "engines": { 156 | "node": "*" 157 | } 158 | }, 159 | "node_modules/deepmerge": { 160 | "version": "4.2.2", 161 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 162 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 163 | "engines": { 164 | "node": ">=0.10.0" 165 | } 166 | }, 167 | "node_modules/dom-serializer": { 168 | "version": "1.4.1", 169 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", 170 | "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", 171 | "dependencies": { 172 | "domelementtype": "^2.0.1", 173 | "domhandler": "^4.2.0", 174 | "entities": "^2.0.0" 175 | }, 176 | "funding": { 177 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 178 | } 179 | }, 180 | "node_modules/domelementtype": { 181 | "version": "2.3.0", 182 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 183 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 184 | "funding": [ 185 | { 186 | "type": "github", 187 | "url": "https://github.com/sponsors/fb55" 188 | } 189 | ] 190 | }, 191 | "node_modules/domhandler": { 192 | "version": "4.3.1", 193 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", 194 | "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", 195 | "dependencies": { 196 | "domelementtype": "^2.2.0" 197 | }, 198 | "engines": { 199 | "node": ">= 4" 200 | }, 201 | "funding": { 202 | "url": "https://github.com/fb55/domhandler?sponsor=1" 203 | } 204 | }, 205 | "node_modules/domutils": { 206 | "version": "2.8.0", 207 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", 208 | "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", 209 | "dependencies": { 210 | "dom-serializer": "^1.0.1", 211 | "domelementtype": "^2.2.0", 212 | "domhandler": "^4.2.0" 213 | }, 214 | "funding": { 215 | "url": "https://github.com/fb55/domutils?sponsor=1" 216 | } 217 | }, 218 | "node_modules/entities": { 219 | "version": "2.2.0", 220 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", 221 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", 222 | "funding": { 223 | "url": "https://github.com/fb55/entities?sponsor=1" 224 | } 225 | }, 226 | "node_modules/escape-string-regexp": { 227 | "version": "4.0.0", 228 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 229 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 230 | "engines": { 231 | "node": ">=10" 232 | }, 233 | "funding": { 234 | "url": "https://github.com/sponsors/sindresorhus" 235 | } 236 | }, 237 | "node_modules/html-entities": { 238 | "version": "2.3.3", 239 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", 240 | "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" 241 | }, 242 | "node_modules/htmlparser2": { 243 | "version": "6.1.0", 244 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", 245 | "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", 246 | "funding": [ 247 | "https://github.com/fb55/htmlparser2?sponsor=1", 248 | { 249 | "type": "github", 250 | "url": "https://github.com/sponsors/fb55" 251 | } 252 | ], 253 | "dependencies": { 254 | "domelementtype": "^2.0.1", 255 | "domhandler": "^4.0.0", 256 | "domutils": "^2.5.2", 257 | "entities": "^2.0.0" 258 | } 259 | }, 260 | "node_modules/inherits": { 261 | "version": "2.0.4", 262 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 263 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 264 | }, 265 | "node_modules/is-plain-object": { 266 | "version": "5.0.0", 267 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", 268 | "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", 269 | "engines": { 270 | "node": ">=0.10.0" 271 | } 272 | }, 273 | "node_modules/lodash.assignin": { 274 | "version": "4.2.0", 275 | "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", 276 | "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==" 277 | }, 278 | "node_modules/lodash.bind": { 279 | "version": "4.2.1", 280 | "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", 281 | "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==" 282 | }, 283 | "node_modules/lodash.defaults": { 284 | "version": "4.2.0", 285 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 286 | "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" 287 | }, 288 | "node_modules/lodash.filter": { 289 | "version": "4.6.0", 290 | "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", 291 | "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==" 292 | }, 293 | "node_modules/lodash.flatten": { 294 | "version": "4.4.0", 295 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 296 | "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" 297 | }, 298 | "node_modules/lodash.foreach": { 299 | "version": "4.5.0", 300 | "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", 301 | "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" 302 | }, 303 | "node_modules/lodash.map": { 304 | "version": "4.6.0", 305 | "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", 306 | "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==" 307 | }, 308 | "node_modules/lodash.merge": { 309 | "version": "4.6.2", 310 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 311 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" 312 | }, 313 | "node_modules/lodash.pick": { 314 | "version": "4.4.0", 315 | "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", 316 | "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" 317 | }, 318 | "node_modules/lodash.reduce": { 319 | "version": "4.6.0", 320 | "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", 321 | "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" 322 | }, 323 | "node_modules/lodash.reject": { 324 | "version": "4.6.0", 325 | "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", 326 | "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==" 327 | }, 328 | "node_modules/lodash.some": { 329 | "version": "4.6.0", 330 | "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", 331 | "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" 332 | }, 333 | "node_modules/nanoid": { 334 | "version": "3.3.4", 335 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 336 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", 337 | "bin": { 338 | "nanoid": "bin/nanoid.cjs" 339 | }, 340 | "engines": { 341 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 342 | } 343 | }, 344 | "node_modules/node-fetch": { 345 | "version": "2.6.7", 346 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 347 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 348 | "dependencies": { 349 | "whatwg-url": "^5.0.0" 350 | }, 351 | "engines": { 352 | "node": "4.x || >=6.0.0" 353 | }, 354 | "peerDependencies": { 355 | "encoding": "^0.1.0" 356 | }, 357 | "peerDependenciesMeta": { 358 | "encoding": { 359 | "optional": true 360 | } 361 | } 362 | }, 363 | "node_modules/nth-check": { 364 | "version": "1.0.2", 365 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", 366 | "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", 367 | "dependencies": { 368 | "boolbase": "~1.0.0" 369 | } 370 | }, 371 | "node_modules/parse-srcset": { 372 | "version": "1.0.2", 373 | "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", 374 | "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" 375 | }, 376 | "node_modules/picocolors": { 377 | "version": "1.0.0", 378 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 379 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 380 | }, 381 | "node_modules/postcss": { 382 | "version": "8.4.14", 383 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", 384 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", 385 | "funding": [ 386 | { 387 | "type": "opencollective", 388 | "url": "https://opencollective.com/postcss/" 389 | }, 390 | { 391 | "type": "tidelift", 392 | "url": "https://tidelift.com/funding/github/npm/postcss" 393 | } 394 | ], 395 | "dependencies": { 396 | "nanoid": "^3.3.4", 397 | "picocolors": "^1.0.0", 398 | "source-map-js": "^1.0.2" 399 | }, 400 | "engines": { 401 | "node": "^10 || ^12 || >=14" 402 | } 403 | }, 404 | "node_modules/readable-stream": { 405 | "version": "3.6.0", 406 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 407 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 408 | "dependencies": { 409 | "inherits": "^2.0.3", 410 | "string_decoder": "^1.1.1", 411 | "util-deprecate": "^1.0.1" 412 | }, 413 | "engines": { 414 | "node": ">= 6" 415 | } 416 | }, 417 | "node_modules/safe-buffer": { 418 | "version": "5.2.1", 419 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 420 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 421 | "funding": [ 422 | { 423 | "type": "github", 424 | "url": "https://github.com/sponsors/feross" 425 | }, 426 | { 427 | "type": "patreon", 428 | "url": "https://www.patreon.com/feross" 429 | }, 430 | { 431 | "type": "consulting", 432 | "url": "https://feross.org/support" 433 | } 434 | ] 435 | }, 436 | "node_modules/sanitize-html": { 437 | "version": "2.7.0", 438 | "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.7.0.tgz", 439 | "integrity": "sha512-jfQelabOn5voO7FAfnQF7v+jsA6z9zC/O4ec0z3E35XPEtHYJT/OdUziVWlKW4irCr2kXaQAyXTXDHWAibg1tA==", 440 | "dependencies": { 441 | "deepmerge": "^4.2.2", 442 | "escape-string-regexp": "^4.0.0", 443 | "htmlparser2": "^6.0.0", 444 | "is-plain-object": "^5.0.0", 445 | "parse-srcset": "^1.0.2", 446 | "postcss": "^8.3.11" 447 | } 448 | }, 449 | "node_modules/source-map-js": { 450 | "version": "1.0.2", 451 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 452 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 453 | "engines": { 454 | "node": ">=0.10.0" 455 | } 456 | }, 457 | "node_modules/string_decoder": { 458 | "version": "1.3.0", 459 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 460 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 461 | "dependencies": { 462 | "safe-buffer": "~5.2.0" 463 | } 464 | }, 465 | "node_modules/tr46": { 466 | "version": "0.0.3", 467 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 468 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 469 | }, 470 | "node_modules/truncate-html": { 471 | "version": "1.0.4", 472 | "resolved": "https://registry.npmjs.org/truncate-html/-/truncate-html-1.0.4.tgz", 473 | "integrity": "sha512-FpDAlPzpJ3jlZiNEahRs584FS3jOSQafgj4cC9DmAYPct6uMZDLY625+eErRd43G35vGDrNq3i7b4aYUQ/Bxqw==", 474 | "dependencies": { 475 | "@types/cheerio": "^0.22.8", 476 | "cheerio": "0.22.0" 477 | } 478 | }, 479 | "node_modules/util-deprecate": { 480 | "version": "1.0.2", 481 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 482 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 483 | }, 484 | "node_modules/webidl-conversions": { 485 | "version": "3.0.1", 486 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 487 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 488 | }, 489 | "node_modules/whatwg-url": { 490 | "version": "5.0.0", 491 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 492 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 493 | "dependencies": { 494 | "tr46": "~0.0.3", 495 | "webidl-conversions": "^3.0.0" 496 | } 497 | } 498 | }, 499 | "dependencies": { 500 | "@tweetback/canonical": { 501 | "version": "2.0.27", 502 | "resolved": "https://registry.npmjs.org/@tweetback/canonical/-/canonical-2.0.27.tgz", 503 | "integrity": "sha512-kfSBGynrL/nDBp0oQyCXo5geZi2+q9A63IrmtKaVHCmPMhFs6U8USnnFB3Ldft7XKEyDjM/EGFB7SOnT09OwqA==" 504 | }, 505 | "@types/cheerio": { 506 | "version": "0.22.31", 507 | "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", 508 | "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==", 509 | "requires": { 510 | "@types/node": "*" 511 | } 512 | }, 513 | "@types/node": { 514 | "version": "18.0.0", 515 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", 516 | "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==" 517 | }, 518 | "boolbase": { 519 | "version": "1.0.0", 520 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 521 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" 522 | }, 523 | "cheerio": { 524 | "version": "0.22.0", 525 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", 526 | "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", 527 | "requires": { 528 | "css-select": "~1.2.0", 529 | "dom-serializer": "~0.1.0", 530 | "entities": "~1.1.1", 531 | "htmlparser2": "^3.9.1", 532 | "lodash.assignin": "^4.0.9", 533 | "lodash.bind": "^4.1.4", 534 | "lodash.defaults": "^4.0.1", 535 | "lodash.filter": "^4.4.0", 536 | "lodash.flatten": "^4.2.0", 537 | "lodash.foreach": "^4.3.0", 538 | "lodash.map": "^4.4.0", 539 | "lodash.merge": "^4.4.0", 540 | "lodash.pick": "^4.2.1", 541 | "lodash.reduce": "^4.4.0", 542 | "lodash.reject": "^4.4.0", 543 | "lodash.some": "^4.4.0" 544 | }, 545 | "dependencies": { 546 | "dom-serializer": { 547 | "version": "0.1.1", 548 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", 549 | "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", 550 | "requires": { 551 | "domelementtype": "^1.3.0", 552 | "entities": "^1.1.1" 553 | } 554 | }, 555 | "domelementtype": { 556 | "version": "1.3.1", 557 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 558 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 559 | }, 560 | "domhandler": { 561 | "version": "2.4.2", 562 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", 563 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", 564 | "requires": { 565 | "domelementtype": "1" 566 | } 567 | }, 568 | "domutils": { 569 | "version": "1.7.0", 570 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", 571 | "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", 572 | "requires": { 573 | "dom-serializer": "0", 574 | "domelementtype": "1" 575 | } 576 | }, 577 | "entities": { 578 | "version": "1.1.2", 579 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", 580 | "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" 581 | }, 582 | "htmlparser2": { 583 | "version": "3.10.1", 584 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", 585 | "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", 586 | "requires": { 587 | "domelementtype": "^1.3.1", 588 | "domhandler": "^2.3.0", 589 | "domutils": "^1.5.1", 590 | "entities": "^1.1.1", 591 | "inherits": "^2.0.1", 592 | "readable-stream": "^3.1.1" 593 | } 594 | } 595 | } 596 | }, 597 | "css-select": { 598 | "version": "1.2.0", 599 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 600 | "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", 601 | "requires": { 602 | "boolbase": "~1.0.0", 603 | "css-what": "2.1", 604 | "domutils": "1.5.1", 605 | "nth-check": "~1.0.1" 606 | }, 607 | "dependencies": { 608 | "dom-serializer": { 609 | "version": "0.2.2", 610 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", 611 | "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", 612 | "requires": { 613 | "domelementtype": "^2.0.1", 614 | "entities": "^2.0.0" 615 | } 616 | }, 617 | "domutils": { 618 | "version": "1.5.1", 619 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 620 | "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", 621 | "requires": { 622 | "dom-serializer": "0", 623 | "domelementtype": "1" 624 | }, 625 | "dependencies": { 626 | "domelementtype": { 627 | "version": "1.3.1", 628 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 629 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 630 | } 631 | } 632 | } 633 | } 634 | }, 635 | "css-what": { 636 | "version": "2.1.3", 637 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", 638 | "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" 639 | }, 640 | "deepmerge": { 641 | "version": "4.2.2", 642 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 643 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" 644 | }, 645 | "dom-serializer": { 646 | "version": "1.4.1", 647 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", 648 | "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", 649 | "requires": { 650 | "domelementtype": "^2.0.1", 651 | "domhandler": "^4.2.0", 652 | "entities": "^2.0.0" 653 | } 654 | }, 655 | "domelementtype": { 656 | "version": "2.3.0", 657 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 658 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" 659 | }, 660 | "domhandler": { 661 | "version": "4.3.1", 662 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", 663 | "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", 664 | "requires": { 665 | "domelementtype": "^2.2.0" 666 | } 667 | }, 668 | "domutils": { 669 | "version": "2.8.0", 670 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", 671 | "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", 672 | "requires": { 673 | "dom-serializer": "^1.0.1", 674 | "domelementtype": "^2.2.0", 675 | "domhandler": "^4.2.0" 676 | } 677 | }, 678 | "entities": { 679 | "version": "2.2.0", 680 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", 681 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" 682 | }, 683 | "escape-string-regexp": { 684 | "version": "4.0.0", 685 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 686 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" 687 | }, 688 | "html-entities": { 689 | "version": "2.3.3", 690 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", 691 | "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" 692 | }, 693 | "htmlparser2": { 694 | "version": "6.1.0", 695 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", 696 | "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", 697 | "requires": { 698 | "domelementtype": "^2.0.1", 699 | "domhandler": "^4.0.0", 700 | "domutils": "^2.5.2", 701 | "entities": "^2.0.0" 702 | } 703 | }, 704 | "inherits": { 705 | "version": "2.0.4", 706 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 707 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 708 | }, 709 | "is-plain-object": { 710 | "version": "5.0.0", 711 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", 712 | "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" 713 | }, 714 | "lodash.assignin": { 715 | "version": "4.2.0", 716 | "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", 717 | "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==" 718 | }, 719 | "lodash.bind": { 720 | "version": "4.2.1", 721 | "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", 722 | "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==" 723 | }, 724 | "lodash.defaults": { 725 | "version": "4.2.0", 726 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 727 | "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" 728 | }, 729 | "lodash.filter": { 730 | "version": "4.6.0", 731 | "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", 732 | "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==" 733 | }, 734 | "lodash.flatten": { 735 | "version": "4.4.0", 736 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 737 | "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" 738 | }, 739 | "lodash.foreach": { 740 | "version": "4.5.0", 741 | "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", 742 | "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" 743 | }, 744 | "lodash.map": { 745 | "version": "4.6.0", 746 | "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", 747 | "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==" 748 | }, 749 | "lodash.merge": { 750 | "version": "4.6.2", 751 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 752 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" 753 | }, 754 | "lodash.pick": { 755 | "version": "4.4.0", 756 | "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", 757 | "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" 758 | }, 759 | "lodash.reduce": { 760 | "version": "4.6.0", 761 | "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", 762 | "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" 763 | }, 764 | "lodash.reject": { 765 | "version": "4.6.0", 766 | "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", 767 | "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==" 768 | }, 769 | "lodash.some": { 770 | "version": "4.6.0", 771 | "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", 772 | "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" 773 | }, 774 | "nanoid": { 775 | "version": "3.3.4", 776 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 777 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" 778 | }, 779 | "node-fetch": { 780 | "version": "2.6.7", 781 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 782 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 783 | "requires": { 784 | "whatwg-url": "^5.0.0" 785 | } 786 | }, 787 | "nth-check": { 788 | "version": "1.0.2", 789 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", 790 | "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", 791 | "requires": { 792 | "boolbase": "~1.0.0" 793 | } 794 | }, 795 | "parse-srcset": { 796 | "version": "1.0.2", 797 | "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", 798 | "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" 799 | }, 800 | "picocolors": { 801 | "version": "1.0.0", 802 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 803 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 804 | }, 805 | "postcss": { 806 | "version": "8.4.14", 807 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", 808 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", 809 | "requires": { 810 | "nanoid": "^3.3.4", 811 | "picocolors": "^1.0.0", 812 | "source-map-js": "^1.0.2" 813 | } 814 | }, 815 | "readable-stream": { 816 | "version": "3.6.0", 817 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 818 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 819 | "requires": { 820 | "inherits": "^2.0.3", 821 | "string_decoder": "^1.1.1", 822 | "util-deprecate": "^1.0.1" 823 | } 824 | }, 825 | "safe-buffer": { 826 | "version": "5.2.1", 827 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 828 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 829 | }, 830 | "sanitize-html": { 831 | "version": "2.7.0", 832 | "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.7.0.tgz", 833 | "integrity": "sha512-jfQelabOn5voO7FAfnQF7v+jsA6z9zC/O4ec0z3E35XPEtHYJT/OdUziVWlKW4irCr2kXaQAyXTXDHWAibg1tA==", 834 | "requires": { 835 | "deepmerge": "^4.2.2", 836 | "escape-string-regexp": "^4.0.0", 837 | "htmlparser2": "^6.0.0", 838 | "is-plain-object": "^5.0.0", 839 | "parse-srcset": "^1.0.2", 840 | "postcss": "^8.3.11" 841 | } 842 | }, 843 | "source-map-js": { 844 | "version": "1.0.2", 845 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 846 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" 847 | }, 848 | "string_decoder": { 849 | "version": "1.3.0", 850 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 851 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 852 | "requires": { 853 | "safe-buffer": "~5.2.0" 854 | } 855 | }, 856 | "tr46": { 857 | "version": "0.0.3", 858 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 859 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 860 | }, 861 | "truncate-html": { 862 | "version": "1.0.4", 863 | "resolved": "https://registry.npmjs.org/truncate-html/-/truncate-html-1.0.4.tgz", 864 | "integrity": "sha512-FpDAlPzpJ3jlZiNEahRs584FS3jOSQafgj4cC9DmAYPct6uMZDLY625+eErRd43G35vGDrNq3i7b4aYUQ/Bxqw==", 865 | "requires": { 866 | "@types/cheerio": "^0.22.8", 867 | "cheerio": "0.22.0" 868 | } 869 | }, 870 | "util-deprecate": { 871 | "version": "1.0.2", 872 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 873 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 874 | }, 875 | "webidl-conversions": { 876 | "version": "3.0.1", 877 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 878 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 879 | }, 880 | "whatwg-url": { 881 | "version": "5.0.0", 882 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 883 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 884 | "requires": { 885 | "tr46": "~0.0.3", 886 | "webidl-conversions": "^3.0.0" 887 | } 888 | } 889 | } 890 | } 891 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-plugin-webmentions", 3 | "version": "2.1.0", 4 | "description": "An eleventy plugin to fetch webmentions and helper methods to display them", 5 | "main": ".eleventy.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Luke Bonaccorsi", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@tweetback/canonical": "^2.0.27", 13 | "html-entities": "^2.3.3", 14 | "node-fetch": "^2.6.6", 15 | "sanitize-html": "^2.7.0", 16 | "truncate-html": "^1.0.4" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/CodeFoodPixels/eleventy-plugin-webmentions.git" 21 | }, 22 | "keywords": [ 23 | "Eleventy", 24 | "11ty", 25 | "webmentions" 26 | ], 27 | "bugs": { 28 | "url": "https://github.com/CodeFoodPixels/eleventy-plugin-webmentions/issues" 29 | }, 30 | "homepage": "https://github.com/CodeFoodPixels/eleventy-plugin-webmentions#readme" 31 | } 32 | -------------------------------------------------------------------------------- /webmentions.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | const fetch = require("node-fetch"); 3 | const truncateHTML = require("truncate-html"); 4 | const sanitizeHTML = require("sanitize-html"); 5 | const { encode } = require("html-entities"); 6 | const canonical = import("@tweetback/canonical"); 7 | 8 | const defaults = { 9 | cacheDirectory: "./_webmentioncache", 10 | cacheTime: 3600, 11 | truncate: true, 12 | maxContentLength: 280, 13 | truncationMarker: "…", 14 | htmlContent: true, 15 | useCanonicalTwitterUrls: true, 16 | sanitizeOptions: { 17 | allowedTags: ["b", "i", "em", "strong", "a", "p"], 18 | allowedAttributes: { 19 | a: ["href"], 20 | }, 21 | }, 22 | sortFunction: (a, b) => 23 | new Date(a.published || a["wm-received"]) - 24 | new Date(b.published || b["wm-received"]), 25 | }; 26 | function Webmentions({ 27 | domain, 28 | token, 29 | cacheDirectory = defaults.cacheDirectory, 30 | cacheTime = defaults.cacheTime, 31 | truncate = defaults.truncate, 32 | maxContentLength = defaults.maxContentLength, 33 | truncationMarker = defaults.truncationMarker, 34 | htmlContent = defaults.htmlContent, 35 | useCanonicalTwitterUrls = defaults.useCanonicalTwitterUrls, 36 | sanitizeOptions = defaults.sanitizeOptions, 37 | sortFunction = defaults.sortFunction, 38 | }) { 39 | if ( 40 | (typeof domain !== "string" && !Array.isArray(domain)) || 41 | domain.length === 0 42 | ) { 43 | throw new Error("Domain must be provided as a string"); 44 | } 45 | 46 | if (!Array.isArray(domain)) { 47 | domain = [domain]; 48 | } 49 | 50 | if ( 51 | (typeof token !== "string" && !Array.isArray(token)) || 52 | token.length === 0 53 | ) { 54 | throw new Error("Token must be provided as a string."); 55 | } 56 | 57 | if (!Array.isArray(token)) { 58 | token = [token]; 59 | } 60 | 61 | function getUrl(idx) { 62 | return `https://webmention.io/api/mentions.jf2?domain=${domain[idx]}&token=${token[idx]}`; 63 | } 64 | 65 | async function fetchWebmentions(idx, since, page = 0) { 66 | const PER_PAGE = 1000; 67 | 68 | const params = `&per-page=${PER_PAGE}&page=${page}${ 69 | since ? `&since=${since}` : "" 70 | }`; 71 | console.log(`Getting ${getUrl(idx)}${params}`); 72 | const response = await fetch(`${getUrl(idx)}${params}`); 73 | 74 | if (response.ok) { 75 | const feed = await response.json(); 76 | if (feed.children.length === PER_PAGE) { 77 | const olderMentions = await fetchWebmentions(idx, since, page + 1); 78 | 79 | return [...feed.children, ...olderMentions]; 80 | } 81 | return feed.children; 82 | } 83 | 84 | return []; 85 | } 86 | 87 | async function writeToCache(data) { 88 | const filePath = `${cacheDirectory}/webmentions.json`; 89 | const fileContent = JSON.stringify(data, null, 2); 90 | 91 | // create cache folder if it doesnt exist already 92 | if (!(await fs.stat(cacheDirectory).catch(() => false))) { 93 | await fs.mkdir(cacheDirectory); 94 | } 95 | // write data to cache json file 96 | await fs.writeFile(filePath, fileContent); 97 | } 98 | 99 | async function readFromCache() { 100 | const filePath = `${cacheDirectory}/webmentions.json`; 101 | 102 | if (await fs.stat(filePath).catch(() => false)) { 103 | const cacheFile = await fs.readFile(filePath); 104 | return JSON.parse(cacheFile); 105 | } 106 | 107 | return { 108 | lastFetched: null, 109 | children: [], 110 | }; 111 | } 112 | 113 | async function clean(entry) { 114 | const { transform } = await canonical; 115 | 116 | if (useCanonicalTwitterUrls) { 117 | entry.url = transform(entry.url); 118 | entry.author.url = transform(entry.author.url); 119 | } 120 | 121 | if (entry.content) { 122 | if (entry.content.html && htmlContent) { 123 | if (useCanonicalTwitterUrls) { 124 | entry.content.html = entry.content.html.replaceAll( 125 | /"(https:\/\/twitter.com\/(.+?))"/g, 126 | function (match, p1) { 127 | return transform(p1); 128 | } 129 | ); 130 | } 131 | 132 | if (!entry.content.html.match(/^<\/?[a-z][\s\S]*>/)) { 133 | const paragraphs = entry.content.html 134 | .split("\n") 135 | .filter((p) => p.length > 0); 136 | 137 | entry.content.html = `

${paragraphs.join("

")}

`; 138 | } 139 | 140 | const sanitizedContent = sanitizeHTML( 141 | entry.content.html, 142 | sanitizeOptions 143 | ); 144 | 145 | if (truncate) { 146 | const truncatedContent = truncateHTML( 147 | sanitizedContent, 148 | maxContentLength, 149 | { ellipsis: truncationMarker, decodeEntities: true } 150 | ); 151 | 152 | entry.content.value = truncatedContent.replace( 153 | encode(truncationMarker), 154 | truncationMarker 155 | ); 156 | } else { 157 | entry.content.value = sanitizedContent; 158 | } 159 | } else { 160 | entry.content.value = 161 | truncate && entry.content.text.length > maxContentLength 162 | ? `${entry.content.text.substr( 163 | 0, 164 | maxContentLength 165 | )}${truncationMarker}` 166 | : entry.content.text; 167 | 168 | if (htmlContent) { 169 | const paragraphs = entry.content.value 170 | .split("\n") 171 | .filter((p) => p.length > 0); 172 | 173 | entry.content.value = `

${paragraphs.join("

")}

`; 174 | } 175 | } 176 | } 177 | 178 | return entry; 179 | } 180 | 181 | async function get() { 182 | const webmentions = await readFromCache(); 183 | 184 | if ( 185 | !webmentions.lastFetched || 186 | Date.now() - new Date(webmentions.lastFetched) >= cacheTime * 1000 187 | ) { 188 | const feed = await Promise.all( 189 | domain.map((domain, idx) => 190 | fetchWebmentions(idx, webmentions.lastFetched) 191 | ) 192 | ).then((feeds) => feeds.flat()); 193 | 194 | if (feed.length > 0) { 195 | webmentions.lastFetched = new Date().toISOString(); 196 | webmentions.children = [...feed, ...webmentions.children]; 197 | 198 | await writeToCache(webmentions); 199 | } 200 | } 201 | 202 | webmentions.children = await Promise.all( 203 | webmentions.children.sort(sortFunction).map(clean) 204 | ); 205 | 206 | return webmentions; 207 | } 208 | 209 | return { get }; 210 | } 211 | 212 | Webmentions.defaults = defaults; 213 | 214 | module.exports = Webmentions; 215 | --------------------------------------------------------------------------------