Skip to content

拒绝邮件通知延迟!手把手教你把 Waline 评论推送到 Gotify

前言

由于我的博客用的是静态框架 VitePress,本身是没有评论区的,需要额外引入 Waline 才能实现评论功能。

Waline 是一个支持多平台部署多数据库以及多种通知方式的评论系统,在之前的文章《通过 Waline 给你的静态网站添加评论功能 (以 VitePress 为例)》中已经介绍过如何在静态网站中使用它了。

通知方式

尽管 Waline 支持非常多的通知方式——邮件、微信、企业微信、QQ、电报、PushPlus、Discord、飞书……几乎所有的 IM 软件都支持了,但唯独少了对自建通知方式的支持,例如 GotifyNtfy 之类的。

在此之前,我只配置了邮件通知方式,无论是通知我自己还是回复评论,都是通过邮件方式发送的,但在官方文档的 FAQ 中可以看到邮件通知会导致发布评论变慢。

发布评论很慢怎么办?

因为一些技术性原因,在发布评论时垃圾邮件检测、评论通知都是串联操作。

同时,如果评论的数量比较多的话,邮箱里几乎全是 Waline 的通知邮件,并且还会浪费域名邮箱的发件次数。

因此,对于博主的通知最好还是采用其他的通知渠道,只有回复访客的评论时才使用邮件通知。

实现

关闭博主邮件通知

如果你用的就是 Waline 支持的通知方式,例如 QQ、微信等,则不需要特地去关闭博主的邮件通知。

在源码中可以看到开启了其他的通知方式后,邮件通知就会自动关闭。

notify.ts
js
if ([wechat, qq, telegram, qywxAmWechat, pushplus, discord, lark].every((item) => think.isEmpty(item))) {
  mailList.push({ to: AUTHOR, title, content });
}

但如果没有配置这些通知方式,就需要在环境变量中把 DISABLE_AUTHOR_NOTIFY 设置为 true 才可以将博主的新评论通知关闭

危险

需要注意的是这个环境变量会关闭所有的通知渠道

Webhook

对于 Gotify、Ntfy 这些自建的通知方式,在 Issue #2401 有提到可以通过 Webhook 发送,但文档中并没有提及怎么使用,只是简单的说明了 “评论成功后会向 WEBHOOK 配置的地址发送一条 POST 请求”,也没有具体的请求格式。

提示

Webhook 不受 DISABLE_AUTHOR_NOTIFY 控制,只要评论就会发送 POST 请求,因此需要自己实现通知过滤

翻阅源码之后,找到请求的格式如下:

extend.js
js
fetch(WEBHOOK, {
  method: 'POST',
  headers: {
    'content-type': 'application/json'
  },
  body: JSON.stringify({ type, data })
}).then((resp) => resp.json());

这和大多数通知软件要求的格式并不相同,以 Gotify 为例,POST 请求的格式如下:

json
{
  "message": "**Backup** was successfully finished.",
  "priority": 2,
  "title": "Backup"
}

转换格式

那么接下来的思路就比较清晰了,我们可以先接收 Waline 的 Webhook 请求,按照 Gotify 要求的格式修改之后,最后再通过 POST 请求发送给 Gotify。

这一需求可以把上面两种 POST 请求格式发送给 AI,让它直接实现可用的代码,然后部署到 Worker 或者云函数上。

以下是使用 Node.js 原生的 HTTP 模块实现的:

js
const http = require('http');

const AUTHOR_EMAIL = 'hi@zhichao.org';
const SITE_URL = 'https://zhichao.org';
const GOTIFY_URL = `http://127.0.0.1:6666`;
const PORT = 8888;
const IS_FULL = false;

function json(res, obj, code = 200) {
  res.statusCode = code;
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify(obj));
}

http
  .createServer((req, res) => {
    if (req.method !== 'POST') {
      res.statusCode = 405;
      return res.end('only POST');
    }

    if (req.url === '/favicon.ico') {
      return res.end('');
    }

    const token = req.url.slice(1);

    if (!token) {
      return json(res, { error: 'missing token' }, 400);
    }

    const REQUEST_URL = `${GOTIFY_URL}/message?token=${token}`;

    let body = '';

    req.on('data', (chunk) => (body += chunk));

    req.on('end', async () => {
      try {
        const { type, data } = JSON.parse(body);

        const comment = data?.comment || {};
        const reply = data?.reply || null;

        const mail = comment.mail;

        if (mail && mail.toLowerCase() === AUTHOR_EMAIL.toLowerCase()) {
          return json(res, { ignored: true });
        }
        const message = `
👤 评论者
${comment.nick || '匿名'} (${mail || '无'})

📝 评论内容
${comment.comment || ''}

${comment.url?.startsWith('http') ? comment.url : `${SITE_URL + comment.url || ''}`}

${
  reply
    ? `──────回复信息──────

👤 被回复者
${reply.nick || ''} (${reply.mail || ''})

📝 原评论
${reply.comment || ''}
`
    : ''
}`;

        const messageFull = `
👤 评论者:${comment.nick || '匿名'} (${mail || '无'})
🌐 IP:${comment.ip || ''}
🕒 时间:${comment.createdAt || ''}
📱 UA:${comment.ua || ''}
📄 页面:${comment.url?.startsWith('http') ? comment.url : `${SITE_URL + comment.url || ''}`}

📝 评论内容:
${comment.comment || ''}

${
  reply
    ? `──────回复信息──────

👤 被回复者:${reply.nick || ''} (${reply.mail || ''})
📝 原评论:
${reply.comment || ''}
`
    : ''
}`;

        await fetch(REQUEST_URL, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            title: `💬 Waline 评论`,
            message: IS_FULL ? messageFull : message
          })
        });

        return json(res, { success: true });
      } catch (err) {
        console.error(err);
        return json(res, { error: true }, 500);
      }
    });
  })
  .listen(PORT, () => {
    console.log(`Webhook running on http://0.0.0.0:${PORT}`);
  });

假设 Waline、Gotify 以及这一段代码都运行在同一台 VPS 中,此时只需要在 Waline 添加 WEBHOOK 环境变量

yaml
WEBHOOK: http://127.0.0.1:6666/{token}

最终效果就是像下图这样,当用户评论时,由于不再需要给博主发送邮件通知,评论发送的速度会快很多,同时会通过 Webhook 同步给 Gotify 发送通知,几乎零延迟!

测试效果
测试效果

Last updated:

关注微信公众号