delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

27 Dec 2022

迁移到了基于 Remark42 的评论系统

上回书 说到启用了基于 isso 的评论系统,其实当时还发现了一个功能相当强的评论系统 remark42, 但该系统是 Go 写的,并且依赖的其他软件包要比 isso 多不少,因此当时没有下定决心换成这个。

如前文中提到的,我认为减少用户与第三方服务之间的交互,并减少站点保存的潜在的可以识别用户身份的信息是非常重要的。 Remark42 基本上满足了这些需求,并且在包括日志在内的一系列可能暴露用户信息的地方在写入前做了屏蔽处理。 除此之外,它提供了更多的交互功能以及许多常用的 OAuth2 集成(目前我启用了 Telegram、Google、GitHub、Facebook 和 Twitter 的登录),其实现方式避免了上述网站对普通用户的跟踪(用户必须明确选中验证方式之后才会与这些网站产生交互, 而登录状态的访问不会再次和这些网站发生互动)。对于不信任这些服务提供商的用户,还可以选择以电子邮件登录 (系统会发一个 token 到邮箱地址;不过 Remark42 的邮件支持和 isso 类似,也只能通过 SMTP, 加上其中的一些实现不甚完美,因此有可能会被一些系统误认为垃圾邮件。此问题我暂时只是做了少量workaround, 还没有做进一步的修正)。用户发表评论时,Remark42 会自动把评论发到一个我能看到的 Telegram 频道。

Remark42 的官方版本在许多地方是假定用户使用 Docker 的,其官方也提供了一份 FreeBSD binary。 我在今天 commit 了一个 port (www/remark42),它和官方版本的主要区别是:

  • 提供了一个 RC 脚本用来启动、停止服务和完成备份,该脚本的预设值会让 Remark42 按照符合 FreeBSD hier(7) 目录规范去存放文件。
  • 如果没有指定 secret,则脚本会在每次启动时生成一个新的(官方版本要求必须提供一个)。用户应自行生成一个 secret 并配置到 /etc/rc.conf.local
  • 前端部分的build过程使用的是 npm 而不是 pnpm(我目前还没有找到完全阻止后者在build过程中访问网络的方法,如果解决了的话我会再换回用 pnpm)

Remark42 和其他许多使用 Docker 的软件类似,使用了 docker-compose.yaml 通过环境变量, 而不是自己支持的配置文件去进行配置。目前我的做法是让 RC 脚本把 rc.conf 中的变量一一对应到这些环境变量并分别导出。

由于 Remark42 将其前端代码嵌入到了其可执行文件中,因此如果想要修改这些前端代码的话会比较麻烦 (当然,深度定制的话可以自己写一套使用其 REST API 的前端,而不一定需要使用其官方前端)。 另一个问题是它的前端代码中是写死了该实例的 URL,在前端源代码中是 {% REMARK_URL %}, 编译过程中替换为 http://127.0.0.1:8080/,port 或 poudriere 的用户可以通过在 build 过程中传 REMARK_URL 把它改为其他值。

另一个问题是 Remark42 和 Hugo 的集成。我目前使用的这个主题自己提供了深色/浅色两组配色, 而 Remark42 自己也支持同样的功能,因此有必要将两者做成联动的, 但由于两者的脚本都是异步加载,因此并不能依赖 Remark42 自己提供的 window.REMARK42.changeTheme 方法, 因为在调用时可能 window.REMARK42 还不存在。

为了解决这个问题,我目前采用的方法是绕过该问题,在切换部分的脚本中这样做:

  if (typeof window.REMARK42 != "undefined") {
    window.REMARK42.changeTheme('dark');
  }

这样做有个明显的问题:页面加载时可能还是不能正常工作,目前我用的是偷懒的做法,即在 remark_config 中直接从 localStorage 中取出之前保存的数据, 并根据该数据来产生 theme 的值:

theme: localStorage.getItem("scheme") == 'dark'? 'dark' : 'light'

很明显这么做是不完美的,我想,终极的解决方案还是应该自己把前端再做一下,看看有没有时间吧。

目前为止发现的坑:

  1. 代码规模显著比 isso 要大。
  2. 信拼的不是特别对(虽然并不影响客户端解析),此外 SMTP 客户端的实现不完全正确(例如 EHLO 时使用的是 localhost),鉴于我可能是极少数介意此事的人,估计可能也只有亲自去解决了。
  3. 缺少中央管理的界面(这个并不是特别大的问题)。

现有的 isso 数据我参考 Rafael Medina这一篇 的脚本自己做了一个,丢弃了一部分数据(例如 email 等),如果希望把自己的新身份和之前的留言关联到一起的需求的话,请在此留言,我会尽量解决。

总体来说我认为还不错,先跑跑看好了。

20221229更新:稍微拆了一下 Remark42 的前端代码,在没有改 Remark42 前端的前提下做了这么一些变动:

  • Remark42 在大致完成初始化之后,会在 window 中 dispatch 一个 REMARK42::ready 事件,因此可以用这个事件来作为是否去杵 window.REMARK42 的分界线。
  • 为此,设置一个新的 window.addEventListener("REMARK42::ready" 并在其中再判断一次当前的主题(这么做的原因是这一事件发生时, DOMContentLoaded 可能还没处理完)。
  • 然而遗憾的是,该事件并不是 Remark42 完全完成初始化的分界点。直接 changeTheme 可能不足以立即让评论对象以正确的主题显示。我目前还没有找到很好的方法,因此用了非常恶心的workaround:设置超时时间为100ms, 200ms, 400ms, 800ms, 1.6s 和 3.2s 的6次timeout(REMARK42::ready事件只会触发一次,因此最多是多调用6次changeTheme),每次发生时杵一次 changeTheme。这么做并不能完全解决问题:如果客户端速度足够慢的话,可能仍然会出现不一致的现象,但我认为 3.2s 还渲染不出来本站的访客可能也不会太在意界面是否美观了……
  • 最后,由于需要有6次timeout,很难保证是不是有精神病^H^H^H热心用户在3.2s之内点多次切换主题的按钮,因此不能简单地在 timeout 中直接指定状态,而是要从 DOM 中获得该状态。

不得不说我最新的这版脚本看上去简直丑爆了,不过总归是暂时把问题摁住了。