Cactus Comments in WriteFreely
I have been trying out a matrix-based blog comments system.
WriteFreely is a simple self-hosted blogging system. It uses Markdown for content. To let readers subscribe to follow new posts, it supports both RSS and ActivityPub (Fediverse). It has no comments system of its own.
Cactus Comments is a simple self-hosted comments system. It lets us add a comments section to any web page we control, such as a blog. It uses Matrix for the comments.
I describe a self-hosted deployment.
Why Matrix-Based Comments?
Here's a tag line and a longer explanation I just dreamed up:
“Cactus Comments: your comments are your data. Own them!”
Unlike other comment systems, Cactus Comments is not a warehouse, but a post office. Your comments don't just go into a database owned by somebody else. What you write in Cactus Comments is sent from your own regular matrix account. This means you interact on an equal footing: for example, if the site operator may choose not to display your comment, but they don't have control of the only copy of it, you get to keep your own copy of whatever you write, as long as you like. And if you replied to someone, or someone replied to your comment, you are free to keep a copy of their comments too, even after those people close their accounts. That's an example of what we mean when we speak of the freedom brought by using an open system built on open protocols: the freedom not to be controlled absolutely by some other person or company's system, but for you to choose how the system works for you and maintain some control over your own participation.
Installing Cactus Comments and Configuring Matrix
I deploy my matrix server(s) using matrix-docker-ansible-deploy. Its documentation describes setting up Cactus Comments. Basically there is little more to it than adding the following two variables to our Ansible inventory.
matrix_cactus_comments_enabled: true matrix_synapse_allow_guest_access: true
- In the second line, change 'synapse' to 'dendrite' if you are running dendrite instead of synapse.
See “About Guest Access” in the “Why Like This?” section below.
Registering a Site Name
As documented, start a matrix chat with the bot and tell it,
register SITE_NAME where the
SITE_NAME you choose must match the same in the client configuration script below.
JulianF created this DM JulianF invited Cactus Comments
Cactus Comments Created site SITE_NAME for you
If the bot does not respond, double-check the installation. When I first set it up, I tried to take a short cut by running Ansible with only the tags
install-cactus-comments,start but this omitted the needed configuration of synapse and the bot gave no response. After running
ansible-playbook [...] --tags=setup-all,start, as stated in the documentation, it worked.
On the WF blog's “Customize” page, insert the following code in the box under the heading “Custom CSS”. If you want any real custom CSS, you can put it before and/or after this.
Why are we using the “Custom CSS” for a script? It is explained in the “Why Like This?” section below.
Check and adjust the following:
- The script reflects the URL paths where
style.cssare served by the matrix-docker-ansible-deploy described above. If you deploy your matrix homeserver and Cactus Comments server another way, adjust these.
HOMESERVER_DOMAIN(4 places) with the domain of the matrix homeserver that you will be using for the comments system.
defaultHomeserverUrlshould be the URL of the client-server API of the matrix homeserver. Check and adjust if necessary the
matrix.prefix. This should not end with “:8448” even though some examples show it, as that is typically used for the matrix federation API. It may work on some servers, but not on others, depending on how they are set up. The client-server API is typically on the HTTPS default port (443).
SITE_NAMEwith the name you register with the cactus bot, that groups all related comments sections together under single ownership for the purposes of managing the comments.
trueif you prefer. In contrast to the server side guest access configuration, this setting just controls whether we show the option to post a comment without logging in (where the user may optionally provide a display name).
- Check the Cactus Comments client configuration documentation for other options you could add.
On the same WriteFreely “Customize” page, insert the following code in the box under the heading “Post Signature”. If you want any real post signature, it can go before and/or after this comment-section.
There is nothing we need to check or adjust in this part.
Why Like This?
In this section we look at some of the technical particulars and discuss why did them this way.
About Guest Access
If we read the docs for the
matrix_synapse_allow_guest_access setting, they say it is “to allow guest comments”, but unlike the optional client-side setting for guest posting, that we will get to later, Cactus Comments requires guest access to be enabled on its matrix server. What for? It does indeed use a guest account for the (optional) guest mode of posting, but, in addition to that, it also uses a guest account in order to display the existing comments to a user who is not logged in. If we do not enable guest access here, what happens? Cactus Comments displays an error message in red above the comment box, and does not display any existing comments, unless and until the reader logs in to their own matrix account.
I am presently wondering whether allowing guest access will open up my matrix server too much for abuse. I am not sure. The “guest access” feature is little used in the matrix world, and information about it, and managing it, is scarce.
In the meantime, I am deploying this instance of Cactus Comments on a secondary matrix server, rather than my main one. That doesn't stop me using my main matrix account (on my main matrix server) to write my own comments, of course. Matrix is federated, that's the whole point of it, so I and any other commenters can each log in on our chosen matrix homeserver, and still read and write these comments.
Why Use the “Custom CSS” Configuration?
WriteFreely provides some customisation options but not complete control. Of course it is open source so one can do anything at the source code level. Still, for a quick start, it's nice to find a way that uses an unmodified installation.
Another user gave me the hint that WF's “Custom CSS” setting can be mis-used to inject a
<script> element into the
<head> section of the blog page's HTML. It is a typical code-injection hack. Knowing that the content of this box is going to be placed verbatim between
</style> tags in the document head, we start by closing the
<style> element, then we place our
<script> ... </script>, and then we open a new
We are injecting our script in the document
<head>, which comes before our “comment-section” div which we put near the end of the HTML document. The parameter
node: "#comment-section" tells
initComments to look up the “comment-section” element. Because in-line script is executed as soon as it is encountered, a simple attempt here to look up the later “comment-section” element would fail: that later part of the DOM content has not yet been loaded at this point. Instead we need to wait until the DOM content is loaded before we will be able to look up the “comment-section” element. That is why we use
Two ways to supply the
node parameter are documented. The alternative to passing in a
"#comment-section" is to look up the required element before we call
initComments, and pass in a reference to the node object like this:
node: document.getElementById("comment-section"). But that does not change the requirement that the DOM content needs to be loaded first.
If we placed our script at the end of the HTML document, after the “comment-section” div, that would change things. Then this delayed execution would not be necessary.
(I learnt the general form of this solution from Mitja Felicijan who noted it in the Cactus Comments discussion room last October. I changed it to use
DOMContentLoaded instead of
window.addEventListener('load'), after I read in MDN says “It is a common mistake to use
DOMContentLoaded would be more appropriate.“)
commentSectionId needs to be unique per blog post, for the way most of us want our blogs to behave. The documentation (both in Cactus Comments and in matrix-docker-ansible-deploy) shows it configured as a static value such as “1”. Doing that would share the same set of comments across all posts, not what we want.
Different blog systems have different ways of identifying a post. WriteFreely includes a canonical URL in the document head. For a published post, it looks like:
<link rel="canonical" href="https://wrily.foad.me.uk/the-post-slug" />
Cactus Comments uses the
commentSectionId to form a matrix room name, something like,
#comments_wrily_%2Fthe-post-slug:HOMESERVER_DOMAIN. It stores the comments for this blog post in this room. The room name prefix
#comments_ is configured on the server side, and the next part
wrily is the
SITE_NAME we configured on the client side.
Moderation, Spam control, Administration
There is an overview of moderation options in the Cactus Comments Moderation documentation page.
Cactus Comments provides limited moderation facilities on its own. It creates a moderation room where it says,
🚨 If you ban someone from one of your comment sections, I'll make sure they're banned from all of your comment sections 👊 If you add a moderator to this room, I'll make sure they have the same permissions across all your comment sections 👮♀️👮♂️
For more sophisticated spam control, you can deploy a generic matrix moderation tool such as Draupnir.
It is not yet possible (at the time of writing) to hide or “quarantine” comments until you approve them.
What Does the End Result Look Like?
If my deployment is working properly, you should be able to see the result here in this blog page. Please respect this, leave only relevant comments, and don't use it for testing.
- 2023-09 Updated 'Moderation' section.
Follow/Feedback/Contact: RSS feed · Fedi follow this blog: @email@example.com · use the Cactus Comments box above · matrix me · Fedi follow me · email me · julian.foad.me.uk Donate: via Liberapay All posts © Julian Foad and licensed CC-BY-ND except quotes, translations, or where stated otherwise