From de2cfc6fef3382affbd5cea6fd6eab6012c64199 Mon Sep 17 00:00:00 2001 From: Gordon Pedersen Date: Wed, 14 Dec 2022 11:08:47 +1100 Subject: [PATCH] Some updates to try and get author info from webmention notes --- .eleventy.js | 109 ++++++++++++++++++++++++-- _content/_data/author.js | 57 -------------- _content/_includes/layout-default.njk | 2 +- _content/_includes/macro-author.njk | 1 + _content/_includes/macro-entry.njk | 5 +- _content/feed/index.html | 2 +- package-lock.json | 36 ++++++++- package.json | 3 +- 8 files changed, 144 insertions(+), 71 deletions(-) delete mode 100644 _content/_data/author.js diff --git a/.eleventy.js b/.eleventy.js index 6cdddce..ed39709 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,4 +1,6 @@ const pluginRss = require("@11ty/eleventy-plugin-rss"); +const EleventyFetch = require("@11ty/eleventy-fetch"); +const { mf2 } = require("microformats-parser"); module.exports = function(eleventyConfig) { @@ -34,8 +36,92 @@ module.exports = function(eleventyConfig) { return collectionApi.getFilteredByGlob("_content/notes/*.md").reverse(); }); - eleventyConfig.addCollection("feed", function(collectionApi) { - return collectionApi.getAllSorted().reverse().filter(item => { + async function getAuthor(author) { + if(!author) author = { + "display_name": "death.au", + "url": "https://death.id.au", // this is the domain your webfinger is on + "avatar": "/assets/img/avatar-tt.svg" // avatar to use by default + } + + if(!author.url) author.url = author.uri + let authorURL = new URL(author.url) + + if(!author.display_name) author.display_name = author.name || author.preferredUsername || author.username + if(!author.handle) author.handle = `@${author.display_name}@${authorURL.hostname}` + if(!author.avatar) author.avatar = author.photo || author.image || author.img + if(!author.canonical_uri) author.canonical_uri = author.url + + try { + // options for the cached fetch request + const options = { + duration: "1d", // 1 day + type: "json", // also supports "text" or "buffer" + fetchOptions: { + headers: { + 'Content-Type': 'application/activity+json' + } + } + } + + // get the webfinger + handle = author.handle.startsWith('@') ? author.handle.substring(1) : author.handle; + let webfinger = await EleventyFetch(`${authorURL.origin}/.well-known/webfinger?resource=acct:${handle}`, options); + + // get the canonical uri as well + let canonical_uri = webfinger?.links?.find(link => link.rel == "canonical_uri") + if(canonical_uri) author.canonical_uri = canonical_uri.href + + // get the rel="self" link from the webfinger + let self = webfinger?.links?.find(link => link.rel == "self"); + + if(self) { + // fetch the profile information + if(self.type) options.fetchOptions.headers['Content-Type'] = self.type; + try{ + const json = await EleventyFetch(self.href, options); + const isJson = typeof json === 'object' && !!json + if(isJson){ + // apply the supplied author information to the result + json.handle = author.handle + json.avatar = author.avatar + json.display_name = author.display_name || json.preferredUsername + json.canonical_uri = author.canonical_uri || json.url[0] + + // this is now the author object + author = json + } + else { + // try parsing microformats + const parsed = mf2(json, { baseUrl: new URL(self.href).origin }) + if(parsed?.items?.length && parsed.items[0].properties?.author?.length){ + // if we have an author object from here + const parsedAuthor = Object.fromEntries( + Object.entries(arsed.items[0].properties?.author[0]) + // filter out empty entries + .filter(([key, value]) => (!Array.isArray(value) && !!value) || value.every(v => v)) + // if entry is an array of length 1, collapse it to just key:value + .map(([key, value], index) => Array.isArray(value) && value.length == 1 ? [key, value[0]] : [key, value]) + ) + author = { ...element.author, ...parsedAuthor } + } + } + + } + catch { } + if(!author.canonical_uri) author.canonical_uri = self.href + } + else throw 'Could not find rel="self" link in webfinger' + + return author + } + catch(e) { + console.error("Failed to get webfinger info ", e); + return author + } + } + + eleventyConfig.addCollection("feed", async function(collectionApi) { + let feed = collectionApi.getAllSorted().reverse().filter(item => { let showInFeed = false; if(!item.data.published) return false if(item.filePathStem.startsWith('/notes/')){ @@ -68,12 +154,21 @@ module.exports = function(eleventyConfig) { showInFeed = true; } - // if(showInFeed) { - // item.data.author = - // } - return showInFeed; - }); + }) + + return await Promise.all(feed.map(async item => { + item.data.author = await getAuthor(item.data.author) + return item; + })) + }); + + eleventyConfig.addNunjucksAsyncFilter("getAuthorData", (author, callback) => { + return getAuthor(author).then(author => callback(null, author)).catch(err => callback(err)) + }) + + eleventyConfig.addNunjucksAsyncFilter("await", function(promise) { + return promise.then(res => callback(null, res)).catch(err => callback(err)) }); eleventyConfig.addNunjucksShortcode("getVar", function(varString) { diff --git a/_content/_data/author.js b/_content/_data/author.js deleted file mode 100644 index 1a4d61c..0000000 --- a/_content/_data/author.js +++ /dev/null @@ -1,57 +0,0 @@ -const EleventyFetch = require("@11ty/eleventy-fetch"); - -module.exports = async function() { - // set these default options - let author = { - "handle": "@death.au@death.id.au", - "display_name": "death.au", - "url": "https://death.id.au", // this is the domain your webfinger is on - "avatar": "/assets/img/avatar-tt.svg" // avatar to use by default - } - - try { - // options for the cached fetch request - const options = { - duration: "1d", // 1 day - type: "json", // also supports "text" or "buffer" - fetchOptions: { - headers: { - 'Content-Type': 'application/activity+json' - } - } - } - - // get the webfinger - handle = author.handle.startsWith('@') ? author.handle.substring(1) : author.handle; - let webfinger = await EleventyFetch(`${author.url}/.well-known/webfinger?resource=acct:${handle}`, options); - - // get the canonical uri as well - let canonical_uri = webfinger?.links?.find(link => link.rel == "canonical_uri") - if(canonical_uri) author.canonical_uri = canonical_uri.href - - // get the rel="self" link from the webfinger - let self = webfinger?.links?.find(link => link.rel == "self"); - - if(self) { - // fetch the profile information - if(self.type) options.fetchOptions.headers['Content-Type'] = self.type; - json = await EleventyFetch(self.href, options); - - // apply the supplied author information to the result - json.handle = author.handle - json.avatar = author.avatar - json.display_name = author.display_name || json.preferredUsername - json.canonical_uri = author.canonical_uri || json.url[0] - - // this is now the author object - author = json - } - else throw 'Could not find rel="self" link in webfinger' - - return author - } - catch(e) { - console.error("Failed to get webfinger info ", e); - return author - } -}; \ No newline at end of file diff --git a/_content/_includes/layout-default.njk b/_content/_includes/layout-default.njk index 1e31049..07f180f 100644 --- a/_content/_includes/layout-default.njk +++ b/_content/_includes/layout-default.njk @@ -6,7 +6,7 @@ --- {% from "macro-entry.njk" import entryMacro %} -{{ entryMacro(ctx(), url, content) }} +{{ entryMacro(ctx(), author, url, content) }} {% layoutblock 'foot' %} diff --git a/_content/_includes/macro-author.njk b/_content/_includes/macro-author.njk index b85fbb6..c5670f2 100644 --- a/_content/_includes/macro-author.njk +++ b/_content/_includes/macro-author.njk @@ -1,4 +1,5 @@ {% macro authorMacro(author) %} + {# {{ author | getAuthorData | log }} #} {{ author.icon.name if author.icon.name else diff --git a/_content/_includes/macro-entry.njk b/_content/_includes/macro-entry.njk index 0fc100e..06dc6b3 100644 --- a/_content/_includes/macro-entry.njk +++ b/_content/_includes/macro-entry.njk @@ -1,9 +1,8 @@ {% from "macro-card-head.njk" import cardHeadMacro %} {% from "macro-summary.njk" import summaryMacro %} -{% macro entryMacro(item, url, content, summaryOnly=false) %} - +{% macro entryMacro(item, author, url, content, summaryOnly=false) %}
- {{ cardHeadMacro(item.author, item.date, url) }} + {{ cardHeadMacro(author, item.date, url) }}
{{ summaryMacro(item, url) }} {% if item.postType == 'article' and summaryOnly %} diff --git a/_content/feed/index.html b/_content/feed/index.html index 26cbde9..bbd4c63 100644 --- a/_content/feed/index.html +++ b/_content/feed/index.html @@ -11,7 +11,7 @@ eleventyExcludeFromCollections: true
    {% for item in pagination.items %}
  • - {{ entryMacro(item.data, item.url, item.templateContent, true) }} + {{ entryMacro(item.data, item.data.author, item.url, item.templateContent, true) }}
  • {% endfor %}
diff --git a/package-lock.json b/package-lock.json index 6fa7bc4..d0167cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "devDependencies": { "@11ty/eleventy": "^1.0.2", "@11ty/eleventy-fetch": "^3.0.0", - "@11ty/eleventy-plugin-rss": "^1.2.0" + "@11ty/eleventy-plugin-rss": "^1.2.0", + "microformats-parser": "^1.4.1" } }, "node_modules/@11ty/dependency-tree": { @@ -2223,6 +2224,18 @@ "node": ">= 8" } }, + "node_modules/microformats-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/microformats-parser/-/microformats-parser-1.4.1.tgz", + "integrity": "sha512-BSg9Y/Aik8hvvme/fkxnXMRvTKuVwOeTapeZdaPQ+92DEubyM31iMtwbgFZ1383om643UvfYY5G23E9s1FY2KQ==", + "dev": true, + "dependencies": { + "parse5": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -2529,6 +2542,12 @@ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", "dev": true }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5628,6 +5647,15 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "microformats-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/microformats-parser/-/microformats-parser-1.4.1.tgz", + "integrity": "sha512-BSg9Y/Aik8hvvme/fkxnXMRvTKuVwOeTapeZdaPQ+92DEubyM31iMtwbgFZ1383om643UvfYY5G23E9s1FY2KQ==", + "dev": true, + "requires": { + "parse5": "^6.0.0" + } + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -5845,6 +5873,12 @@ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", "dev": true }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index 5db3bfc..2f09128 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "devDependencies": { "@11ty/eleventy": "^1.0.2", "@11ty/eleventy-fetch": "^3.0.0", - "@11ty/eleventy-plugin-rss": "^1.2.0" + "@11ty/eleventy-plugin-rss": "^1.2.0", + "microformats-parser": "^1.4.1" } }