diff --git a/assets/scss/_single.scss b/assets/scss/_single.scss index b7689a56..22901b98 100644 --- a/assets/scss/_single.scss +++ b/assets/scss/_single.scss @@ -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; diff --git a/content/bookmarks/i-don-t-want-to-host-services-but-i-do-.md b/content/bookmarks/i-don-t-want-to-host-services-but-i-do-.md new file mode 100644 index 00000000..a746e128 --- /dev/null +++ b/content/bookmarks/i-don-t-want-to-host-services-but-i-do-.md @@ -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 don’t want to self-host, and even worse: I think most individuals + shouldn’t 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. + +I’m 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. + +I’ll 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_ diff --git a/data/interactions/.json b/data/interactions/.json new file mode 100644 index 00000000..ec871827 --- /dev/null +++ b/data/interactions/.json @@ -0,0 +1 @@ +{"interactions":[{"guid":"webmentions.io#1795016","emoji":"💬","url":"https://hachyderm.io/@byjp/112115874706330891","comment":"@geffrey @death.au ????☺️ well now I’m 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"}]} diff --git a/data/interactions/calendar.json b/data/interactions/calendar.json new file mode 100644 index 00000000..135a304b --- /dev/null +++ b/data/interactions/calendar.json @@ -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"}]} diff --git a/data/interactions/notes/jgdx0.json b/data/interactions/notes/jgdx0.json new file mode 100644 index 00000000..a19991af --- /dev/null +++ b/data/interactions/notes/jgdx0.json @@ -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"}]} diff --git a/data/interactions/photos/coche.json b/data/interactions/photos/coche.json index 89249164..80dfa39d 100644 --- a/data/interactions/photos/coche.json +++ b/data/interactions/photos/coche.json @@ -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"}}]} diff --git a/data/interactions/photos/la-florida.json b/data/interactions/photos/la-florida.json index aaf6322c..9a072975 100644 --- a/data/interactions/photos/la-florida.json +++ b/data/interactions/photos/la-florida.json @@ -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}} diff --git a/data/interactions/photos/lncsouqdekcc63jpx4wm6pcozi.json b/data/interactions/photos/lncsouqdekcc63jpx4wm6pcozi.json index 36479739..450c522d 100644 --- a/data/interactions/photos/lncsouqdekcc63jpx4wm6pcozi.json +++ b/data/interactions/photos/lncsouqdekcc63jpx4wm6pcozi.json @@ -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 - } + ] } \ No newline at end of file diff --git a/data/interactions/photos/mums-garden.json b/data/interactions/photos/mums-garden.json index f5166fd7..39b7f579 100644 --- a/data/interactions/photos/mums-garden.json +++ b/data/interactions/photos/mums-garden.json @@ -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"}}]} diff --git a/data/interactions/photos/pre-wedding.json b/data/interactions/photos/pre-wedding.json index 2b14eb0b..44092af0 100644 --- a/data/interactions/photos/pre-wedding.json +++ b/data/interactions/photos/pre-wedding.json @@ -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"}]} diff --git a/data/interactions/photos/sunny-brunch.json b/data/interactions/photos/sunny-brunch.json index d20c7392..d8e83b19 100644 --- a/data/interactions/photos/sunny-brunch.json +++ b/data/interactions/photos/sunny-brunch.json @@ -1 +1 @@ -{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/629220605979255535","author":{"name":"Rui Almeida","url":"https://pixelfed.social/RJCA_PT"}}],"reactions":{"⭐️":1}} \ No newline at end of file +{"interactions":[{"emoji":"⭐️","url":"https://pixelfed.social/p/jphastings/629220605979255535","author":{"name":"Rui","url":"https://pixelfed.social/RJCA_PT"}}]} \ No newline at end of file diff --git a/data/interactions/posts/adventure-awaits/a-patagonian-love-story.json b/data/interactions/posts/adventure-awaits/a-patagonian-love-story.json new file mode 100644 index 00000000..cb20d97a --- /dev/null +++ b/data/interactions/posts/adventure-awaits/a-patagonian-love-story.json @@ -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"}]} diff --git a/data/interactions/posts/bookmarks-with-omnivore.json b/data/interactions/posts/bookmarks-with-omnivore.json new file mode 100644 index 00000000..8531876c --- /dev/null +++ b/data/interactions/posts/bookmarks-with-omnivore.json @@ -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"}]} diff --git a/data/interactions/posts/chef-gpt.json b/data/interactions/posts/chef-gpt.json index 851e24c1..a5a68ebe 100644 --- a/data/interactions/posts/chef-gpt.json +++ b/data/interactions/posts/chef-gpt.json @@ -1 +1 @@ -{"interactions":[{"comment":"Ha! Just read this and I have to say I’m not impressed at all. I like my cooking a bit too much to let ChatGPT getting anywhere near it! I don’t 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}} \ No newline at end of file +{"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 I’m not impressed at all. I like my cooking a bit too much to let ChatGPT getting anywhere near it! I don’t 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"}]} diff --git a/data/interactions/posts/webs-missing-communication-faculty.json b/data/interactions/posts/webs-missing-communication-faculty.json new file mode 100644 index 00000000..f314eb75 --- /dev/null +++ b/data/interactions/posts/webs-missing-communication-faculty.json @@ -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"}]} diff --git a/data/interactions/search.json b/data/interactions/search.json new file mode 100644 index 00000000..932cb4a7 --- /dev/null +++ b/data/interactions/search.json @@ -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"}]} diff --git a/layouts/partials/interactions.html b/layouts/partials/interactions.html index 87650012..486fa67e 100644 --- a/layouts/partials/interactions.html +++ b/layouts/partials/interactions.html @@ -1,4 +1,15 @@ -{{ $index := (split .Path "/") | after 1 -}} +{{- define "partials/reactionsvg.html" -}} +{{- if eq .emoji "♥️" }}{{ end -}} +{{- if eq .emoji "⭐️" }}{{ end -}} +{{- if eq .emoji "👍" }}{{ end -}} +{{- if eq .emoji "👏" }}{{ end -}} +{{- if eq .emoji "🔁" }}{{ end -}} +{{- if eq .emoji "💬" }}{{ end -}} + +{{- if .name }}: {{ end -}} +{{- end -}} + +{{- $index := (split .Path "/") | after 1 -}} {{- $interactions := index .Site.Data.interactions $index -}} {{ with $interactions }}
@@ -9,28 +20,28 @@ {{ $most = $count }} {{ $emoji = $thisEm }} {{ end }} - {{ end }} + {{ end -}} - -
Reactions & comments + {{- $interactionMap := dict -}} + {{- range $in := .interactions -}} + {{- $oldList := index $interactionMap $in.emoji }} + {{- $newList := append $in $oldList -}} + {{- $interactionMap = merge $interactionMap (dict $in.emoji $newList) -}} + {{- end -}}
{{ else }} diff --git a/tools/go.work b/tools/go.work new file mode 100644 index 00000000..c07de2d3 --- /dev/null +++ b/tools/go.work @@ -0,0 +1,9 @@ +go 1.21.6 + +use ( + ./archive/goodreads + ./archive/instagram + ./archive/twitter + ./import/omnivore + ./import/webmentionio +) diff --git a/tools/go.work.sum b/tools/go.work.sum new file mode 100644 index 00000000..6aa0c94b --- /dev/null +++ b/tools/go.work.sum @@ -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= diff --git a/tools/import/omnivore/go.work b/tools/import/omnivore/go.work deleted file mode 100644 index c4eec06d..00000000 --- a/tools/import/omnivore/go.work +++ /dev/null @@ -1,3 +0,0 @@ -go 1.21.6 - -use . diff --git a/tools/import/omnivore/go.work.sum b/tools/import/omnivore/go.work.sum deleted file mode 100644 index bfc2806c..00000000 --- a/tools/import/omnivore/go.work.sum +++ /dev/null @@ -1 +0,0 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tools/import/omnivore/main.go b/tools/import/omnivore/main.go index d38c51bf..9feeaf9d 100644 --- a/tools/import/omnivore/main.go +++ b/tools/import/omnivore/main.go @@ -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 { diff --git a/tools/import/webmentionio/.gitignore b/tools/import/webmentionio/.gitignore new file mode 100644 index 00000000..2eea525d --- /dev/null +++ b/tools/import/webmentionio/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/tools/import/webmentionio/go.mod b/tools/import/webmentionio/go.mod new file mode 100644 index 00000000..5381b5cf --- /dev/null +++ b/tools/import/webmentionio/go.mod @@ -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 diff --git a/tools/import/webmentionio/go.sum b/tools/import/webmentionio/go.sum new file mode 100644 index 00000000..3e9cb651 --- /dev/null +++ b/tools/import/webmentionio/go.sum @@ -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= diff --git a/tools/import/webmentionio/interactions.go b/tools/import/webmentionio/interactions.go new file mode 100644 index 00000000..9d8ee37d --- /dev/null +++ b/tools/import/webmentionio/interactions.go @@ -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"` +} diff --git a/tools/import/webmentionio/jf2.go b/tools/import/webmentionio/jf2.go new file mode 100644 index 00000000..4a60d01b --- /dev/null +++ b/tools/import/webmentionio/jf2.go @@ -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 +} diff --git a/tools/import/webmentionio/main.go b/tools/import/webmentionio/main.go new file mode 100644 index 00000000..237cc067 --- /dev/null +++ b/tools/import/webmentionio/main.go @@ -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) + } +} diff --git a/tools/syndicate/shared/types.go b/tools/syndicate/shared/types.go index d547f746..a327ecc2 100644 --- a/tools/syndicate/shared/types.go +++ b/tools/syndicate/shared/types.go @@ -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"`