Published on

在 Hugo 里调用文章的 Twikoo 评论数

Authors
  • avatar
    作者
    老麦

前言

很久之前就知道 Twikoo 官方文档里有个批量调用文章评论数的 API,我也一直有计划着给博客首页的最新文章列表给添加相应显示相对应的评论数。但是碍于自己不会传参,因此一直迟迟未能研究明白到底如何才能实验自己想要的效果。

尝试

最近正在学习基础的 Python 知识,不知咋的就想到用 Python 来将 Twikoo API 所需的参数做成一个 JSON 文件以便调用,现在回想当时自己的想法是真够有意思的。不过还别说,最终还真让我做出来了。

import glob
import yaml
import json

# 获取最后5个文件的slug
file_list = sorted(glob.glob('content/post/*.md'), key=lambda x: int(x.split('/')[-1].split('.')[0]), reverse=True)[:5]
slugs = []

for file_path in file_list:
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            documents = yaml.safe_load_all(file)
            for document in documents:
                if isinstance(document, dict):
                    slug = document.get('slug')
                    if slug:
                        modified_slug = f"/archives/{slug}/"
                        slugs.append(modified_slug)
    except (yaml.parser.ParserError, FileNotFoundError):
        continue

# 将数据写入 JSON 文件
with open('static/slugs.json', 'w') as outfile:
    json.dump(slugs, outfile)

不过这个代码并不适用于所有人,有两个很重要的限制:

  1. content/post 目录下的文件名我都加了序号,如:1.aaa.md2.bbb.md3.ccc.md
  2. 文章里的 yaml 区域里都有一个 slug 的值,就是文章的链接;

运行这个 py 文件就可以在 static 目录下生成一个 slug.json 的文件,里面就是文章的链接,如:

["/archives/twilight-years-leisurely-4/", "/archives/twilight-years-tucao-2/", "/archives/twilight-years-waiting-to-blossom-2/", "/archives/twilight-years-leisurely-3/", "/archives/twilight-years-leisurely-2/"]

这就正好符合 twikoo.getCommentsCount 中所要传递的 urls 参数,这时我们只需将生成的 slug.json 传递进去应该就可以获取到每个文章的评论数了。

<script src="https://cdn.staticfile.org/twikoo/1.6.16/twikoo.all.min.js"></script>
<script>
async function loadJSON() {
  try {
    const response = await fetch('slugs.json')
    const text = await response.text()
    const json = JSON.parse(text)
    const urls = Object.values(json)
    twikoo
      .getCommentsCount({
        envId: '您的环境id',
        urls: urls,
        includeReply: false,
      })
      .then(function (res) {
        res.forEach(function (item) {
          const countElement = document.getElementById(`${item.url}`)
          if (countElement) {
            countElement.textContent = `${item.count}`
          }
        })
      })
      .catch(function (err) {
        console.error(err)
      })
  } catch (error) {
    console.error(error)
  }
}
loadJSON()
</script>

然后在页面里添加一个用来接收返回值的标签,

<span id="{{ .RelPermalink }}"></span> <!-- 添加用于显示评论数量的占位符 -->

这样就可以在最新文章列表上为每一篇文章显示对应的评论数量了,查看更多可浏览「 老麦笔记 」。

laomai.org

改进(推荐)

后来想到每次更新文章都要重新生成 slug.json 着实有点不方便,就想着既然都已经明确知道文章的 {{ .RelPermalink }} 的值与 twikoo.getCommentsCount 需要接收的参数是一致的,那么何必要多此一举生成 slug.json,直接将 li 标签里的 {{ .RelPermalink }} 使用 document.querySelectorgetAttribute 方法来获取存储它的值,并将其赋给变量 relPermalink,再然后就可以将 relPermalink 变量作为参数传递给 Twikoo 的 getCommentsCount 了。

想法有了就好办多了,具体实现的代码如下:

function updateCommentsCount() {
  let liElements = document.querySelectorAll('.latestPosts li')
  let urls = []
  liElements.forEach(function (li) {
    let relPermalink = li.getAttribute('data-relpermalink')
    urls.push(relPermalink)
  })
  twikoo.getCommentsCount({
    envId: '您的环境id',
    urls: urls,
    includeReply: false,
  })
    .then(function (responses) {
      responses.forEach(function (res) {
        let countElement = document.getElementById(res.url)
        if (countElement && typeof res.count !== 'undefined') {
          countElement.textContent = res.count
        } else {
          console.error('Invalid response:', res)
        }
      })
    })
    .catch(function (err) {
      console.error(err)
    })
}
window.addEventListener('load', updateCommentsCount)

至于 Hugo 主题的需要修改的地方可参考,

<ul class="latestPosts">
    {{ range first 5 (where site.RegularPages "Type" "in" "post") }}
    <li class="!pb-2 md:!pb-4 flex gap-1 flex-col md:flex-row opacity-90 md:gap-2" data-relpermalink="{{ .RelPermalink }}">
      <p>
        <a
          class="p-1 border-b border-b-transparent hover:border-b-stone-400 hover:text-stone-900 dark:text-stone-100 hover:opacity-100 duration-300"
          href="{{ .RelPermalink }}"
          >{{ .Title }}</a
        >
      </p>
      <p class="text-stone-400">
        <span class="inline-block align-middle">{{ .Date.Format "Jan 2" }} · ≈{{ .ReadingTime }}min · </span>
        {{ partial "icons/icon" (dict "vendor" "bootstrap" "name" "chat" "className" "inline-block align-middle opacity-70 ml-1") }}
        <span id="{{ .RelPermalink }}" class="inline-block align-middle"></span>
      </p>
    </li>
    {{ end }}
  </ul>

结语

有了想法,实现起来就显得简单多了。还是那一句:折腾得快乐,快乐地折腾。