Import from webmentions.io

This commit is contained in:
JP Hastings-Spital 2024-03-29 22:40:38 +00:00
parent 2566def16d
commit 25eb81de1f
29 changed files with 414 additions and 32 deletions

View file

@ -519,11 +519,22 @@ details {
}
svg {
margin: 0 0.25em 0 0;
margin: 0;
}
}
}
ul.named-reactions {
list-style: none;
margin-top: 1em;
opacity: 0.4;
transition: opacity 0.25s ease-in-out;
&:hover {
opacity: 1;
}
}
ol.comments {
list-style: none;

View file

@ -0,0 +1,43 @@
---
title: I don't want to host services (but I do)
date: "2024-03-28T16:17:01Z"
publishDate: "2023-08-09T16:00:00Z"
bookmarkOf: https://ergaster.org/posts/2023/08/09-i-dont-want-to-host-services-but-i-do/
references:
bookmark:
url: https://ergaster.org/posts/2023/08/09-i-dont-want-to-host-services-but-i-do/
type: entry
name: I don't want to host services (but I do)
summary: 'I dont want to self-host, and even worse: I think most individuals
shouldnt host services for others. Yet, I am self-hosting services and I even
teach people how to do it.'
author: Thibault Martin
tags:
- homelab
- tech
---
A concise and smart article talking to those of us interested in offering hobby networked services to others.
Im still in the mindset that a form of fully decentralised systems could overcome many of the issues mentioned here. My current bet would be on client-side web applications (or just websites) served from content addressed stores (like §IPFS), storing data primarily on local machines (think [fission.codes](https://fission.codes) and the §LocalFirst movement), is going to be a fruitful route.
Ill be looking forward to the next article on the challenges with end-to-end encryption (E2EE)!
### Highlights
> Good backups are:
>
> * done regularly
> * documented
> * tested regularly
> * stored at a different location than the computer powering your services, also called [off-site backups](https://en.wikipedia.org/wiki/Backup#Off-site%5Fdata%5Fprotection)
> * not accessible from a compromised machine in your network
---
> My recommendation to most people putting services online would be: either do it for yourself _only_, or do it as a team with proper structure and processes.
---
> we need to figure out how to do decentralise services and data storage for individuals _at scale_

1
data/interactions/.json Normal file
View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1795016","emoji":"💬","url":"https://hachyderm.io/@byjp/112115874706330891","comment":"@geffrey @death.au ????☺️ well now Im blushing! Thank you!\n\nThe code for the site \u0026 the automation is public \u0026 MIT licenced, so do take anything you find useful; and get in touch if you have any questions!","author":{"name":"JP","url":"https://hachyderm.io/@byjp"},"timestamp":"2024-03-18T09:01:22Z"}]}

View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1758794","emoji":"♥️","url":"https://hachyderm.io/@byjp/111645944873743257#favorited-by-109303710428386973","author":{"name":"Jamie Tanna","url":"https://fed.brid.gy/r/https://www.jvt.me"},"timestamp":"2023-12-27T05:38:33Z"},{"guid":"webmentions.io#1758795","emoji":"⭐️","url":"https://hachyderm.io/@byjp/111645944873743257#favorited-by-109344010117626087","author":{"name":"sass","url":"https://mastodon.social/@sass"},"timestamp":"2023-12-27T05:38:33Z"},{"guid":"webmentions.io#1758521","emoji":"⭐️","url":"https://hachyderm.io/@byjp/111645944873743257#favorited-by-109290756203712091","author":{"name":"Maria Neumayer","url":"https://androiddev.social/@marianeum"},"timestamp":"2023-12-26T10:19:17Z"}]}

View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1790528","emoji":"💬","url":"https://mastodon.social/@MonaApp/112059587175850353","comment":"@byjp This will be fixed in the next update.","author":{"name":"Mona app","url":"https://mastodon.social/@MonaApp"},"timestamp":"2024-03-08T10:26:43Z"}]}

View file

@ -1 +1 @@
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Stephen","url":"https://pixelfed.social/jaguar314"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Stray Cats Observer","url":"https://pixelfed.social/headofcatdept"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Dany","url":"https://mastodon.art/users/danyaero"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Deekshith Allamaneni","url":"https://pixelfed.social/dk"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Tired Sloth","url":"https://pixelfed.social/TiredSloth"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Bryan 🏳️‍🌈 Mastodon","url":"https://hugs.lgbt/users/riversidebryan"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Art Zemon ✅ 🟦","url":"https://babka.social/users/azemon"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"🔁","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Bryan 🏳️‍🌈 Pixelfed","url":"https://pixelfed.social/RiversideBryan"},"timestamp":"0001-01-01T00:00:00Z"}],"reactions":{"⭐️":7,"🔁":1}}
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Stephen","url":"https://pixelfed.social/jaguar314"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Stray Cats Observer","url":"https://pixelfed.social/headofcatdept"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Dany","url":"https://mastodon.art/users/danyaero"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Deekshith Allamaneni","url":"https://pixelfed.social/dk"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Tired Sloth","url":"https://pixelfed.social/TiredSloth"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Bryan 🏳️‍🌈 Mastodon","url":"https://hugs.lgbt/users/riversidebryan"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Art Zemon ✅ 🟦","url":"https://babka.social/users/azemon"}},{"emoji":"🔁","url":"https://pixelfed.social/p/jphastings/634552262586596629","author":{"name":"Bryan 🏳️‍🌈 Pixelfed","url":"https://pixelfed.social/RiversideBryan"}}]}

View file

@ -1 +1 @@
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Dany","url":"https://mastodon.art/users/danyaero"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Myth2844","url":"https://pixelfed.social/myth2844"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Douglas VB","url":"https://mastodon.social/users/douglasvb"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Bryan 🏳️‍🌈 Mastodon","url":"https://hugs.lgbt/users/riversidebryan"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"JP","url":"https://hachyderm.io/users/byjp"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Shura","url":"https://pixelfed.social/Shura"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Nathan Metzger","url":"https://pixelfed.social/Linuxfan724"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Narek","url":"https://pixelfed.social/Kerobian"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"katherine","url":"https://pixelfed.social/frecklefox"}},{"emoji":"🔁","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Oldladymd","url":"https://med-mastodon.com/users/Oldladymd"}},{"emoji":"🔁","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Bryan 🏳️‍🌈 Pixelfed","url":"https://pixelfed.social/RiversideBryan"}}],"reactions":{"⭐️":9,"🔁":2}}
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Dany","url":"https://mastodon.art/users/danyaero"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Myth2844","url":"https://pixelfed.social/myth2844"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Douglas VB","url":"https://mastodon.social/users/douglasvb"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Bryan 🏳️‍🌈 Mastodon","url":"https://hugs.lgbt/users/riversidebryan"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Shura","url":"https://pixelfed.social/Shura"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Nathan Metzger","url":"https://pixelfed.social/Linuxfan724"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Narek","url":"https://pixelfed.social/Kerobian"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"katherine","url":"https://pixelfed.social/frecklefox"}},{"emoji":"🔁","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Oldladymd","url":"https://med-mastodon.com/users/Oldladymd"}},{"emoji":"🔁","url":"https://pixelfed.social/p/jphastings/631278994729871476","author":{"name":"Bryan 🏳️‍🌈 Pixelfed","url":"https://pixelfed.social/RiversideBryan"}}],"reactions":{"⭐️":9,"🔁":2}}

View file

@ -1,6 +1,7 @@
{
"interactions": [
{
"emoji": "💬",
"comment": "Con antenitas de vinil 🤪",
"url": "https://www.instagram.com/p/CoscyoJvPtI9wJtAdE6fJJ1vY6AMB827jvAHUo0/c/17994947992681879/",
"author": {
@ -9,9 +10,5 @@
},
"timestamp": "2023-02-15T20:16:08.000Z"
}
],
"reactions": {
"❤️": 46,
"💬": 1
}
]
}

View file

@ -1 +1 @@
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"JP","url":"https://hachyderm.io/users/byjp"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"Ben!","url":"https://pixelfed.social/bencord0"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"Thomas Zimmermann","url":"https://pixelfed.social/curlingtom"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"Ivan Jurišić","url":"https://pixelfed.social/ijurisic"},"timestamp":"0001-01-01T00:00:00Z"}],"reactions":{"⭐️":4}}
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"Ben!","url":"https://pixelfed.social/bencord0"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"Thomas Zimmermann","url":"https://pixelfed.social/curlingtom"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/619856573800023125","author":{"name":"Ivan Jurišić","url":"https://pixelfed.social/ijurisic"}}]}

View file

@ -1 +1 @@
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/632549785261132608","author":{"name":"alexanderniki","url":"https://pixelfed.social/alexanderniki"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/632549785261132608","author":{"name":"Software Pagan","url":"https://pixelfed.social/softwarepagan"},"timestamp":"0001-01-01T00:00:00Z"},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/632549785261132608","author":{"name":"JP","url":"https://hachyderm.io/users/byjp"},"timestamp":"0001-01-01T00:00:00Z"},{"url":"https://pixelfed.social/p/RiversideBryan/634556807537550283","comment":"Congratulations 🥰","author":{"name":"Bryan 🏳️‍🌈 Pixelfed","url":"https://pixelfed.social/RiversideBryan"},"timestamp":"2023-11-27T01:02:12Z"}],"reactions":{"⭐️":3,"💬":1}}
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/632549785261132608","author":{"name":"alexanderniki","url":"https://pixelfed.social/alexanderniki"}},{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/632549785261132608","author":{"name":"Software Pagan","url":"https://pixelfed.social/softwarepagan"}},{"emoji": "💬", "url":"https://pixelfed.social/p/RiversideBryan/634556807537550283","comment":"Congratulations 🥰","author":{"name":"Bryan 🏳️‍🌈 Pixelfed","url":"https://pixelfed.social/RiversideBryan"},"timestamp":"2023-11-27T01:02:12Z"}]}

View file

@ -1 +1 @@
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/629220605979255535","author":{"name":"Rui Almeida","url":"https://pixelfed.social/RJCA_PT"}}],"reactions":{"⭐️":1}}
{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/629220605979255535","author":{"name":"Rui","url":"https://pixelfed.social/RJCA_PT"}}]}

View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1795406","emoji":"♥️","url":"https://www.jvt.me/mf2/2024/03/oaqea/","author":{"name":"Jamie Tanna","url":"https://www.jvt.me"},"timestamp":"2024-03-19T16:46:00+01:00"}]}

View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1770115","emoji":"⭐️","url":"https://hachyderm.io/@byjp/111793793066838551#favorited-by-108771874996654815","author":{"name":"chrismcleod.dev","url":"https://mastodon.online/@mstrkapowski"},"timestamp":"2024-01-21T14:06:42Z"},{"guid":"webmentions.io#1770114","emoji":"⭐️","url":"https://hachyderm.io/@byjp/111793793066838551#favorited-by-109344010117626087","author":{"name":"sass","url":"https://mastodon.social/@sass"},"timestamp":"2024-01-21T14:06:41Z"}]}

View file

@ -1 +1 @@
{"interactions":[{"comment":"Ha! Just read this and I have to say Im not impressed at all. I like my cooking a bit too much to let ChatGPT getting anywhere near it! I dont think my commitment to science would have been as good as yours, I draw the line at throwing almonds and chickpeas on salmon 😂","url":"https://bsky.app/profile/davidcondemarin.bsky.social/post/3ko2zz752p72q","author":{"name":"David","url":"https://bsky.app/profile/davidcondemarin.bsky.social"},"timestamp":"2024-03-19T18:53:00.000Z"},{"emoji":"❤","url":"https://bsky.app/profile/byjp.me/post/3knrhqsj6ac23/liked-by","author":{"name":"David","url":"https://bsky.app/profile/davidcondemarin.bsky.social"}},{"emoji":"⭐️","url":"https://hachyderm.io/@byjp/112102305952063134/favourites","author":{"name":"Maria Neumayer","url":"https://androiddev.social/@marianeum"}}],"reactions":{"❤️":1,"⭐️":1,"💬":1}}
{"interactions":[{"guid":"webmentions.io#1795504","emoji":"💬","url":"https://bsky.app/profile/davidcondemarin.bsky.social/post/3ko2zz752p72q","comment":"Ha! Just read this and I have to say Im not impressed at all. I like my cooking a bit too much to let ChatGPT getting anywhere near it! I dont think my commitment to science would have been as good as yours, I draw the line at throwing almonds and chickpeas on salmon ????","author":{"name":"David","url":"https://bsky.app/profile/davidcondemarin.bsky.social"},"timestamp":"2024-03-19T18:53:44Z"},{"guid":"webmentions.io#1795367","emoji":"♥","url":"https://bsky.app/profile/byjp.me/post/3knrhqsj6ac23#liked_by_did:plc:yqsc5vd6x67yofv5fmu5qkax","author":{"name":"","url":"https://bsky.app/profile/davidcondemarin.bsky.social"},"timestamp":"2024-03-19T18:53:14Z"},{"guid":"webmentions.io#1794075","emoji":"⭐️","url":"https://hachyderm.io/@byjp/112102305952063134#favorited-by-109290756203712091","author":{"name":"Maria Neumayer","url":"https://androiddev.social/@marianeum"},"timestamp":"2024-03-17T02:11:11Z"}]}

View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1782674","emoji":"🔁","url":"https://hachyderm.io/@byjp/111951750976061859#reblogged-by-111257712064575715","author":{"name":"Bill Seitz","url":"https://toolsforthought.social/@billseitz"},"timestamp":"2024-02-19T02:40:36Z"},{"guid":"webmentions.io#1782381","emoji":"⭐️","url":"https://hachyderm.io/@byjp/111951750976061859#favorited-by-109168960959032263","author":{"name":"Olivier Forget","url":"https://social.tchncs.de/@teleclimber"},"timestamp":"2024-02-18T10:41:56Z"},{"guid":"webmentions.io#1782366","emoji":"⭐️","url":"https://hachyderm.io/@byjp/111951750976061859#favorited-by-108221131497108772","author":{"name":"Luci for dyeing","url":"https://merveilles.town/@zens"},"timestamp":"2024-02-18T10:07:14Z"}]}

View file

@ -0,0 +1 @@
{"interactions":[{"guid":"webmentions.io#1790184","emoji":"🔁","url":"https://hachyderm.io/@byjp/112053398317306907#reblogged-by-108221131497108772","author":{"name":"Luci for dyeing","url":"https://merveilles.town/@zens"},"timestamp":"2024-03-07T09:11:23Z"},{"guid":"webmentions.io#1790182","emoji":"🔁","url":"https://hachyderm.io/@byjp/112053398317306907#reblogged-by-111626810820500784","author":{"name":"\"You do you\" is Eu-gen-ics.","url":"https://disabled.social/@beadsland"},"timestamp":"2024-03-07T09:11:21Z"},{"guid":"webmentions.io#1790183","emoji":"⭐️","url":"https://hachyderm.io/@byjp/112053398317306907#favorited-by-108221131497108772","author":{"name":"Luci for dyeing","url":"https://merveilles.town/@zens"},"timestamp":"2024-03-07T09:11:21Z"}]}

View file

@ -1,4 +1,15 @@
{{ $index := (split .Path "/") | after 1 -}}
{{- define "partials/reactionsvg.html" -}}
{{- if eq .emoji "♥️" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(55% 71 27)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>{{ end -}}
{{- if eq .emoji "⭐️" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(65% 71 81)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>{{ end -}}
{{- if eq .emoji "👍" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(55% 71 270)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-up"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg>{{ end -}}
{{- if eq .emoji "👏" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(45% 71 270)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather"><path d="M20.248 12.654a1.61 1.61 0 0 1 2.366 1.203 1.6 1.6 0 0 1-.796 1.597l-10.522 6.075a5.471 5.471 0 0 1-4.16.554 5.5 5.5 0 0 1-3.334-2.555l-1.565-2.71a6.42 6.42 0 0 1-.198-6.064l3.128-6.321a1.65 1.65 0 0 1 2.237-.732 1.65 1.65 0 0 1 .868 1.71l-.5 3.314 9.016-5.205a1.6 1.6 0 0 1 2.185.586 1.61 1.61 0 0 1-.58 2.194l1.42-.82a1.61 1.61 0 0 1 2.18.555 1.61 1.61 0 0 1-.59 2.2l-1.42.82a1.6 1.6 0 0 1 2.185.585 1.61 1.61 0 0 1-.56 2.229zM18.427 6.28 12.86 9.495M20.037 9.069l-5.568 3.215M16.074 15.064l4.174-2.41"/></svg>{{ end -}}
{{- if eq .emoji "🔁" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(55% 71 130)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>{{ end -}}
{{- if eq .emoji "💬" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(65% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-circle"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>{{ end -}}
{{- if .name }}: {{ end -}}
{{- end -}}
{{- $index := (split .Path "/") | after 1 -}}
{{- $interactions := index .Site.Data.interactions $index -}}
{{ with $interactions }}
<div class="interactions">
@ -9,28 +20,28 @@
{{ $most = $count }}
{{ $emoji = $thisEm }}
{{ end }}
{{ end }}
{{ end -}}
<details open>
<summary>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-circle"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>
Reactions & comments
</summary>
{{- $interactionMap := dict -}}
{{- range $in := .interactions -}}
{{- $oldList := index $interactionMap $in.emoji }}
{{- $newList := append $in $oldList -}}
{{- $interactionMap = merge $interactionMap (dict $in.emoji $newList) -}}
{{- end -}}
<ul class="reactions">
{{ range $thisEm, $count := .reactions }}
{{ if gt $count 0 }}
{{- range $thisEm, $items := $interactionMap -}}
{{- $count := len $items -}}
{{- if gt $count 0 -}}
<li>
{{ if eq $thisEm "❤️" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(55% 71 27)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>{{ end -}}
{{ if eq $thisEm "⭐️" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(65% 71 81)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>{{ end -}}
{{ if eq $thisEm "👍" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(55% 71 270)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-up"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg>{{ end -}}
{{ if eq $thisEm "🔁" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(55% 71 130)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>{{ end -}}
{{ if eq $thisEm "💬" }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="lch(65% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-circle"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>{{ end -}}
{{- $count }}
{{ partial "partials/reactionsvg.html" (dict "emoji" $thisEm) }}
{{ $count }}
</li>
{{ end }}
{{ end }}
@ -47,6 +58,20 @@
</li>
{{ end }}
</ol>
<ul class="named-reactions">
{{- range $thisEm, $items := $interactionMap -}}
{{- if ne $thisEm "💬" -}}
<li>
{{ partial "partials/reactionsvg.html" (dict "emoji" $thisEm "name" true) -}}
{{- range $i, $in := $items -}}
<a href="{{ $in.url }}" target="_blank">{{ $in.author.name }}</a>
{{- if ne $i (sub (len $items) 1) }}, {{ end -}}
{{- end -}}
</li>
{{- end -}}
{{ end }}
</ul>
</details>
</div>
{{ else }}

9
tools/go.work Normal file
View file

@ -0,0 +1,9 @@
go 1.21.6
use (
./archive/goodreads
./archive/instagram
./archive/twitter
./import/omnivore
./import/webmentionio
)

4
tools/go.work.sum Normal file
View file

@ -0,0 +1,4 @@
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=

View file

@ -1,3 +0,0 @@
go 1.21.6
use .

View file

@ -1 +0,0 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -33,6 +33,21 @@ func main() {
fmt.Fprint(os.Stderr, "OMNIVORE_API_KEY is not set")
os.Exit(1)
}
rootDir := "./"
if len(os.Args) > 1 {
rootDir = os.Args[1]
}
outputDir := path.Join(rootDir, "content/bookmarks")
if !isDir(outputDir) {
fmt.Printf(
"Usage: %s [directory]\n 'directory' should be the root of your hugo blog (default: ./)\n",
path.Base(os.Args[0]),
)
os.Exit(1)
}
// Make the GraphQL request
articles, err := omnivoreArticles(
"in:archive has:highlights sort:updated-des",
@ -43,8 +58,6 @@ func main() {
os.Exit(1)
}
outputDir := "../../../content/bookmarks"
for _, article := range articles {
if err := outputArticle(article, outputDir); err != nil {
fmt.Fprintf(os.Stderr, "Failed to output article: %v\n", err)
@ -52,6 +65,15 @@ func main() {
}
}
func isDir(pathStr string) bool {
st, err := os.Stat(pathStr)
if os.IsNotExist(err) {
return false
}
return st.IsDir()
}
var hashtags = regexp.MustCompile(`#\w+`)
func outputArticle(article Article, outputDir string) error {

1
tools/import/webmentionio/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env

View file

@ -0,0 +1,11 @@
module github.com/by-jp/www.byjp.me/tools/import/webmentionio
go 1.21.6
require (
github.com/by-jp/www.byjp.me/tools/syndicate v0.0.0-00010101000000-000000000000
github.com/joho/godotenv v1.5.1
github.com/kelseyhightower/envconfig v1.4.0
)
replace github.com/by-jp/www.byjp.me/tools/syndicate => ../../syndicate

View file

@ -0,0 +1,4 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=

View file

@ -0,0 +1,9 @@
package main
import (
synd "github.com/by-jp/www.byjp.me/tools/syndicate/shared"
)
type InteractionFile struct {
Interactions []synd.Interaction `json:"interactions"`
}

View file

@ -0,0 +1,37 @@
package main
type Mentions struct {
Type string
Name string
Children []Mention
}
type Mention struct {
Type string
Author Author
URL string
// Fallback on WebmentionReceived as fallback for Published
Published string // (likes often don't have timestamps)
WebmentionReceived string `json:"wm-received"`
WebmentionID uint `json:"wm-id"`
WebmentionTarget string `json:"wm-target"`
Content struct {
Text string
}
Summary struct {
ContentType string `json:"content-type"`
Value string
}
// The kind of action, eg. like-of, in-reply-to, repost-of
WebmentionProperty string `json:"wm-property"`
WebmentionPrivate bool `json:"wm-private"`
}
type Author struct {
Type string
Name string
Photo string
URL string
}

View file

@ -0,0 +1,204 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
synd "github.com/by-jp/www.byjp.me/tools/syndicate/shared"
)
const webmentions = "https://webmention.io/api/mentions.jf2?domain=%s&token=%s"
const siteTarget = "https://www.byjp.me/"
type Config struct {
Domain string
Token string
Root string
}
func main() {
check(godotenv.Load())
var c Config
check(envconfig.Process("webmentionio", &c))
mentions, err := retrieveMentions(c)
check(err)
pathChecker := checkValidPath(path.Join(c.Root, "content"))
for _, m := range mentions.Children {
pathStr, interaction, err := parseJF2(m, pathChecker)
switch err {
case nil:
// Proceed
case ErrNoEntry, ErrIsPrivate, ErrIncorrectTarget:
// Skip
continue
default:
// Stop
check(err)
}
dst := path.Join(c.Root, "data/interactions", pathStr+".json")
addErr := addInteraction(dst, interaction)
if addErr != nil {
fmt.Fprintf(os.Stderr, "Couldn't save interaction %s to %s: %v\n", interaction.GUID, dst, addErr)
}
}
}
func checkValidPath(contentDir string) func(string) bool {
return func(pathStr string) bool {
pathDir := path.Join(contentDir, pathStr)
std, err := os.Stat(pathDir)
if err == nil && std.IsDir() {
return true
}
pathFile := pathDir + ".md"
stf, err := os.Stat(pathFile)
if err == nil && stf.Mode().IsRegular() {
return true
}
return false
}
}
func addInteraction(jsonPath string, newIn synd.Interaction) error {
inf := InteractionFile{}
rf, err := os.Open(jsonPath)
if err == nil {
if err := json.NewDecoder(rf).Decode(&inf); err != nil && err != io.EOF {
return err
}
} else if !os.IsNotExist(err) {
return err
}
rf.Close()
added := false
for idx, in := range inf.Interactions {
if in.GUID == newIn.GUID {
inf.Interactions[idx] = newIn
added = true
}
}
if !added {
inf.Interactions = append(inf.Interactions, newIn)
}
if err := os.MkdirAll(path.Dir(jsonPath), 0755); err != nil {
return err
}
wf, err := os.OpenFile(jsonPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer wf.Close()
return json.NewEncoder(wf).Encode(inf)
}
var ErrNoEntry = errors.New("JF2 item isn't an entry")
var ErrIsPrivate = errors.New("JF2 item is set to private")
var ErrIncorrectTarget = errors.New("JF2 item describes a different target site")
func parseJF2(m Mention, isValidPath func(string) bool) (string, synd.Interaction, error) {
if m.Type != "entry" {
return "", synd.Interaction{}, ErrNoEntry
}
if m.WebmentionPrivate {
return "", synd.Interaction{}, ErrIsPrivate
}
if !strings.HasPrefix(m.WebmentionTarget, siteTarget) {
return "", synd.Interaction{}, ErrIncorrectTarget
}
if strings.HasPrefix(m.URL, siteTarget) {
return "", synd.Interaction{}, ErrIncorrectTarget
}
sitePath := strings.TrimRight(m.WebmentionTarget[len(siteTarget):], "/")
if !isValidPath(sitePath) {
return "", synd.Interaction{}, ErrIncorrectTarget
}
i := synd.Interaction{
GUID: fmt.Sprintf("webmentions.io#%d", m.WebmentionID),
URL: m.URL,
Comment: m.Content.Text,
Author: synd.Author{
// TODO: Handle Bluesky no name on likes
Name: m.Author.Name,
URL: m.Author.URL,
// TODO: Photo?
},
}
p, err := time.Parse(time.RFC3339, m.Published)
if err != nil || p.IsZero() {
p, _ = time.Parse(time.RFC3339, m.WebmentionReceived)
}
i.Timestamp = p
// TODO: very long content?
// TODO: If wm-source != URL then acting on a reference, not on a syndicated post
switch m.WebmentionProperty {
case "like-of":
if strings.Contains(m.Author.URL, "/@") {
i.Emoji = "⭐️"
} else {
i.Emoji = "♥️"
}
case "repost-of":
i.Emoji = "🔁"
case "in-reply-to":
i.Emoji = "💬"
case "mention-of":
i.Comment = "_A wibble_"
i.Emoji = "🗣️"
}
return sitePath, i, nil
}
func retrieveMentions(c Config) (Mentions, error) {
url := webmentionsURL(c.Domain, c.Token)
res, err := http.Get(url)
if err != nil {
return Mentions{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
body, _ := io.ReadAll(res.Body)
return Mentions{}, fmt.Errorf("Non 200 response code: %v\n%s", res.StatusCode, body)
}
var m Mentions
return m, json.NewDecoder(res.Body).Decode(&m)
}
func webmentionsURL(domain, token string) string {
return fmt.Sprintf(webmentions, domain, token)
}
func check(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

View file

@ -25,7 +25,9 @@ type Post struct {
}
type Interaction struct {
// eg. Repost is 🔁, Facebook is 👍, Instagram is ♥️, Mastodon is ⭐️, Medium is 👏
// Some unique identifier that remains consistent for this interaction
GUID string `json:"guid"`
// eg. Repost is 🔁, Facebook is 👍, Instagram & Bluesky are ♥️, Mastodon is ⭐️, Medium is 👏
Emoji string `json:"emoji,omitempty"`
// The URL of the original interaction
URL string `json:"url"`