[HN Gopher] Adding comments to your static blog with Mastodon ___________________________________________________________________ Adding comments to your static blog with Mastodon Author : ognarb Score : 159 points Date : 2020-12-29 14:57 UTC (8 hours ago) (HTM) web link (carlschwan.eu) (TXT) w3m dump (carlschwan.eu) | kordlessagain wrote: | I use Github Pages for my blog and just found this little gem for | implementing comments using issues: https://danyow.net/using- | github-issues-for-blog-comments/ | modeless wrote: | Interesting. GitHub recently added a "discussions" feature. I | wonder if it would be more suitable for this use. | strenholme wrote: | I switched over from a hacked together bbPress site for | MaraDNS support to using GitHub discussions (which I also | link to for blog comments). It's one less site for me to | maintain. | FalconSensei wrote: | What I do with Hugo is: | | - Added reddit and twitter attributes on the frontmatter | | - After publishing and sharing to both services, I paste reddit | link/tweet id to the frontmatter and rebuild | | - On the blogs footer, I just embed both posts, so people can see | how many replies, and also click to be redirected reply on each | respective service. | | Although I would love something more automated (and preferably | with in-site response), at least this is a good flow for the | readers. | mstibbard wrote: | This seems like a good approach (acknowledging it limits you to | only comments from reddit/twitter users). How do you embed the | posts so it shows # of replies? | asim wrote: | You can embed a comments API using micro - m3o.com. Sign-up, run | the comments service, copy/paste some JS code and there you have | it. | unicornporn wrote: | How about https://webmention.io/ ? Fediverse-friendly and | Twitter-friendly. | FalconSensei wrote: | seems like a cool idea, but without any demo to try, it's hard | to go for it | unicornporn wrote: | It's implemented here: https://www.kickscondor.com/meat- | computer | dmje wrote: | All looked great until this: "You can use your Mastodon account | to reply to this post" and then you lost 99% of your possible | commenters | progval wrote: | document.getElementById('mastodon-comments-list').innerHTML = | data['descendants'].reduce(function(prev, reply) { | mastodonComment = ` ... `; return | prev + DOMPurify.sanitize(mastodonComment); }, ''); | | Why is it implemented like this? It looks quadratic, while it's | trivial (and more intuitive IMO) to make it linear: | document.getElementById('mastodon-comments-list').innerHTML = | data['descendants'].map(function(reply) { return | DOMPurify.sanitize(` ... `); | }).join(''); | brunoluiz wrote: | It could be an issue for websites with huge audiences and many | comments, but I (guess) this wouldn't be a big performance | issue for most personal static websites. Probably, the audience | size and number of comments is not big enough to cause any | trouble. | | Sometimes, a hacky solution is just enough ;) | progval wrote: | That's another issue I didn't want to mention to not be too | negative: the script relies entirely on the Mastodon instance | to filter out the spam. | jlokier wrote: | It's not quadratic if the way reduce() and JS inlining work | together causes this: return prev + | DOMPurify.sanitize(mastodonComment); | | to end up behaving like this: reduceValue = | reduceValue + DOMPurify.sanitize(mastodonComment); | | which ends up behaving like this: reduceValue | += DOMPurify.sanitize(mastodonComment); | | which ends up being a sequence of in-place string appends. | | In any good implementation, a sequence of repeated appends | takes linear time in the total length of all the appended | strings, like join(''). | | I've just tested this theory in Safari 14.0.2 with a JS console | one-liner. On this browser, both versions take linear time not | quadratic, and the test corresponding to the first version even | runs slightly faster. | | The time difference is small though, and you're right, the | second version is explicitly linear time. If I had tested with | a much older browser (maybe IE8), I think the first version | would have taken quadratic time. | brabel wrote: | Your alternative is also pretty bad as it will generate lots of | intermediate Strings before joining them up... Just use a | forEach instead and append directly to the DOM: | let root = document.getElementById('mastodon-comments-list'); | data['descendants'].forEach(reply => { let div = | document.createElement('div'); div.innerHtml = | DOMPurify.sanitize( ... ); root.append(div); | }); | jlokier wrote: | "Pretty bad" is unwarranted in my opinion. Your alternative | adds overhead of creating an extra div for each intermediate | string in the code it replaces. It's trivial overhead, and | may be a small number, but exactly the same applies to the | overhead of temporary strings - they are comparable. | | Intermediate string length doesn't matter for time complexity | here. If they are short they will be fast. If they are long, | the time to scan and parse in DOMPurify and innerHTML will | dominate. | | After building the DOM, your extra divs are processed every | time that section of the DOM is styled and rendered, not just | once. If the number of extra divs is low enough that this is | negligible, so is the number of intermediate strings in the | alternative code. | | So I wouldn't assume your version is faster at setting up, | and it may be marginally slower later on repeated renderings. | I'd profile both versions, maybe with Browserscope (which | probably has a test for this particular question already). | | However if I couldn't profile and was asked my guess at the | fastest version, my guess is the string-join version. I'd be | more concerned with whether concatenating | DOMPurify.sanitize() strings is guaranteed to maintain the | same security properties. | hombre_fatal wrote: | If anyone wanted to provide half useful bikeshedding, they | could make the "Load Comments" button disable and change to | "Loading..." while it's being fetched. Right now there's no | feedback and the user is likely to click multiple times | wondering if it's working on a slower connection like mine. | | They could also probably just html-escape the few foreign | values rather than bringing an HTML parser to the entire | template. | rattray wrote: | Generating intermediate strings is much less expensive than | painting to the DOM multiple times. | failrate wrote: | "One of the biggest disadvantages of static site generators is | that they are static and can't include comments." At first, I | misread this as "One of the biggest advantages...", and I still | think that is the correct conclusion. | CarelessExpert wrote: | I achieve this using https://brid.gy/ (or https://fed.brid.gy/ if | you want your blog to appear as a first class member of the | Fediverse), though in my case I just syndicate out to Twitter. I | then collect the webmentions using https://webmention.io/. | | The nice thing about this is I get a breadth of methods for | receiving comments/reactions across multiple platforms, including | anything that directly supports webmention (such as | https://micro.blog/, where my posts are also syndicated), without | having to do any of the platform-specific wiring myself. | andrewshadura wrote: | Great, thanks! I was just about to write something similar, and | now I think I don't need to | StavrosK wrote: | This looks fantastic, does it support plain RSS? My blog is a | static site, but I'd like it to be exposed to the fediverse. | CarelessExpert wrote: | Just check the site for details. The short version is you | need an Atom feed. | StavrosK wrote: | I did, looks like it needs webmentions, which needs server- | side code. | CarelessExpert wrote: | That's what webmention.io is for. It'll receive | webmentions on your behalf and then expose an API to pull | em down. You hit that API during your static site build | or do it dynamically with some client side JS. Either way | it allows you to avoid hosting any services yourself. | StavrosK wrote: | Oh I see, thank you. | ognarb wrote: | I keep hearing about webmentions in my fediverse bubble, maybe | one day I should take a look at it. | input_sh wrote: | For the unaware, they're essentially identical to WordPress' | Pingbacks. You configure your website so that other websites | let you know whenever they link to you. | CarelessExpert wrote: | Honestly, I set up webmentions to use brid.gy, not just for | the sake of using webmentions. I wanted a two-way integration | between my static blog and Twitter, and this solution has | done the job nicely. | | That I ended up with direct webmention support was just a | bonus as far as I'm concerned. | FalconSensei wrote: | So, would brid.gy automatically post your blog post to | twitter? That's what you mean by two-way integration? | CarelessExpert wrote: | Correct. Using the right microformats, it'll auto | syndicate notes as tweets and full posts as tweets with a | summary and a link. All my site has to do is shoot out a | webmention to brid.gy and it does the rest. | | Then when people react (reply, like, etc) it'll proxy | those interactions back as webmentions. | rattray wrote: | Nice! How much does this approach cost (ballpark)? | CarelessExpert wrote: | Other than my time to set it up? Zero. All the services I | mentioned are operated for free (or can be self-hosted). | criddell wrote: | Are the interactions valuable enough that if it weren't | available for free, you would be willing to pay for it each | month? | CarelessExpert wrote: | I'm not doing anything to build a readership, so my | content doesn't get a lot of interactions. So for me, no. | | If I had a real twitter following, though, I might have a | different answer. | chrismorgan wrote: | The technique demonstrated here for producing the markup is bad: | | 1. The use of DOMPurify is either unnecessary or insufficient. | Specifically consider reply.content, which is provided from the | server as HTML. (I believe it's the only one that's supposed to | be serialised HTML; all the other fields are text.) If the | backend guarantees that you get a clean DOM, DOMPurify is | unnecessary+. But if it's possible for a user to control the | markup in that field completely, then DOMPurify as configured is | insufficient, because although it blocks XSS and JavaScript | execution, it doesn't filter out remote resource loading and CSS, | which can be almost as harmful. Trivial example, <a | href=//malicious.example style=position:fixed;inset:0>pwned</a>. | Given the type of content, you probably want to either blacklist | quite a few things (e.g. the style attribute, and the img, | picture, source, audio and video tags), or whitelist a small set | of things. | | 2. Various fields that could hypothetically contain magic | characters are dropped in with no escaping. If they _do_ contain | magic characters like < and &, you've just messed up the display | and opened the way for malicious resource loading and problem 3 | below. Even if they are supposed to be unable to contain a magic | character (e.g. I'm going to _guess_ that reply.account.username | is safe), it's probably a good idea to escape them anyway just in | case (perhaps an API change later makes it possible), and to | guard against errant copy-pasters and editors of the code that | don't know what they're doing. Perhaps at some point you'll add | or switch to reply.account.display_name, which probably _can_ | contain < and &. | | 3. The markup is produced by mixing static templating with user- | provided input, and sanitisation is performed on the whole thing. | It's important when doing this sort of templating that each user- | provided input be escaped or sanitised _by itself in isolation_ , | not as part of a whole that has been concatenated. Otherwise you | can mess with the DOM tree accidentally or deliberately. Suppose, | for example, that reply.content could contain | `</div></div></div><img src=//ad.example alt="Legitimate-looking | in-stream ad"><div class="mastodon-comment">...<div | class="content">...<div class="mastodon-comment-content">...`. So | this means: | | * Apply attributes and text nodes to a real DOM (e.g. `img = new | Image(); img.src = reply.account.avatar_static; | avatar.append(img)`), or escape them in the HTML serialisation | (e.g. `<img src="${reply.account.avatar_static.replace(/&/g, | "&").replace(/"/g, """)}">`). | | * Do HTML sanitisation on _just the user input_ , e.g. | DOMPurify.sanitize(reply.content). | | A part of my problem with the code as written is that, purely | from looking at the code, I can see that there _may_ well be | various security holes. I require knowledge of the backend also | before I can judge whether there _are_ security holes. Where | possible, it's best to write the code in such a way that you | _know_ that it's safe, or that it's not--try not to depend on | subtle things like "the _particular fields_ that we access happen | to be unable to contain angle brackets, ampersands and quotes", | because they're fragile. | | Incidentally, it would also be more efficient to run DOMPurify | with the RETURN_DOM_FRAGMENT option, and append that, rather than | concatenating it to a string and setting innerHTML. Saves a | pointless serialisation/deserialisation round trip, and avoids | any possibility of new mXSS vulnerabilities that might be | discovered in the future. (I don't really understand why | DOMPurify defaults to emitting a string. I can't remember seeing | a single non-demo use of DOMPurify where the string is preferable | to a DOM fragment.) | | -- | | + Though if the server sanitised arbitrary user-provided | HTML/MathML/SVG, I probably don't trust it as much as I trust | DOMPurify, for things that end up in the DOM. There are some | pretty crazy subtleties in things like HTML serialisation round- | tripping of HTML, SVG and MathML content. There's fun reading in | the changelogs and security patches. | ognarb wrote: | Thanks a lot for all this nice tips. I applied most of them to | my blog post. | floatingatoll wrote: | Does syndicating third-party content onto your blog in this way | expose you to legal liability for publishing that content? Are | deletions and takedowns synced back from Mastodon, such as when | someone is taken offline for breaking local law with their | Mastodon instance, or would their content remain published on | your blog? | ognarb wrote: | I can technically filter comments pretty easily from the js | script. When a comment is removed from mastodon it is also | removed from the blog. | llbeansandrice wrote: | Do people like comments on blogs? I can't find the links right | now but I've found a couple of people talking about how they used | to have comments on their blog and decided to get rid of it since | they would occasionally become a curation/moderation nightmare. | input_sh wrote: | Personally, no. They distract me from content and I often find | myself jumping to comments directly if I know there are some. | | I'm from a region where they're pretty common on news sites, so | I've created a Firefox add-on that hides the comments from | about 40 local websites that I chose via Alexa ranks. ___________________________________________________________________ (page generated 2020-12-29 23:00 UTC)