Some updates to try and get author info from webmention notes

This commit is contained in:
Gordon Pedersen 2022-12-14 11:08:47 +11:00
parent e63127fc13
commit de2cfc6fef
8 changed files with 144 additions and 71 deletions

View file

@ -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) {

View file

@ -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
}
};

View file

@ -6,7 +6,7 @@
---
{% from "macro-entry.njk" import entryMacro %}
{{ entryMacro(ctx(), url, content) }}
{{ entryMacro(ctx(), author, url, content) }}
{% layoutblock 'foot' %}
<script src="/assets/js/relative-time.js"></script>

View file

@ -1,4 +1,5 @@
{% macro authorMacro(author) %}
{# {{ author | getAuthorData | log }} #}
<a class="p-author h-card u-url" href="{{ author.canonical_uri if author.canonical_uri else author.url }}">
<img class="u-photo small-avatar" alt="{{ author.icon.name if author.icon.name else "Avatar for " + author.display_name }}" src="{{ author.icon.url if author.icon.url else author.avatar }}"/>
<span class="display-name">

View file

@ -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) %}
<div class="h-entry type-{{ item.postType }}">
{{ cardHeadMacro(item.author, item.date, url) }}
{{ cardHeadMacro(author, item.date, url) }}
<div class="card-body">
{{ summaryMacro(item, url) }}
{% if item.postType == 'article' and summaryOnly %}

View file

@ -11,7 +11,7 @@ eleventyExcludeFromCollections: true
<ul>
{% for item in pagination.items %}
<li>
{{ entryMacro(item.data, item.url, item.templateContent, true) }}
{{ entryMacro(item.data, item.data.author, item.url, item.templateContent, true) }}
</li>
{% endfor %}
</ul>

36
package-lock.json generated
View file

@ -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",

View file

@ -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"
}
}