使用 dictd 搭建 DICT 字典服务器

by wzyboy on

本文介绍如何使用 dictd 搭建一个 DICT 字典服务器,并将 Kindle 字典转换、导入其中使用。

一、DICT 协议

说实话,我没想到查字典(自然语言意味)这件事居然有个专门的网络协议。DICT 协议是 1997 年由 RFC 2229 确立的。使用 2628/tcp 通信,它和互联网田园时代的其他协议(如 HTTP/1.0、SMTP)一样,都非常简单,就是直接在 TCP 连接里使用人类可读的英文单词,进行一问一答的交流。比如这是我用 telnet 进行的一次简单的 DICT 会话(粗体文字为我输入的部分):

$ telnet 127.0.0.1 2628
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 herus.wzyboy.org dictd 1.12.1/rf on Linux 4.16.12-1-ARCH <auth.mime> <55.25902.1528442646@herus.wzyboy.org>
SHOW DB
110 2 databases present
OALECD7 "Oxford Advanced Learner's English-Chinese Dictionary, 7th Edition"
XDHY5 "现代汉语词典(第5版)"
.
250 ok
DEFINE OALECD7 abandon
150 1 definitions retrieved
151 "abandon" OALECD7 "Oxford Advanced Learner's English-Chinese Dictionary, 7th Edition"
abandon
   <h3>abandon</h3> ★<b>aban•don</b> <font color="#000080">
/əˈbændən/</font><br/> □<b><i><font color="#400000"> noun</font></i></b>
[U]<br/> (<i>formal</i>) an uncontrolled way of behaving that shows that
sb does not care what other people think 放任;放纵 :<br/> <p
[...]
</strong>(<i>literary</i>) to feel an emotion so strongly that you can
feel nothing else 陷入,沉湎于(某种情感) :<br/> <p align="left"/><blockquote><i>◊
He abandoned himself to despair. 他陷入绝望。 </i></blockquote>
.
250 ok [d/m/c = 1/0/16; 0.000r 0.000u 0.000s]
QUIT
221 bye [d/m/c = 0/0/0; 17.000r 0.000u 0.000s]
Connection closed by foreign host.

以上会话中我先用 SHOW DB 命令查询这台 DICT 服务器上有哪些字典数据库可用,然后用 DEFINE 命令查询了许多人背单词第一个背到的单词的 abandon,最后我用 QUIT 命令与服务器告别。这数字状态码、这用点(.)表示传输完成的习惯,和 SMTP 是多么相似!

DICT 协议的标准实现是 dictd,它包含服务端 dictd 和命令行客户端 dict,以及一个用于编译数据库的 dictfmt。dictd 在主流发行版上均可使用包管理器直接安装。

二、转换 Kindle 字典

dictd 只是一个 DICT 的实现,并不自带字典,因此你需要自己准备字典。dictd 有自己的数据库格式,网上也有不少别人制作好的可以直接给 dictd 用的字典,但中文的几乎没有。因此我们需要自己转换。

在《Anki —— 高效的间隔重复记忆软件》一文中我已经涉及过字典转换的问题。简单又方便的高质量字典来源之一是 Kindle 字典。如果你拥有一本 Kindle 阅读器,Amazon 会赠送你几十本各种语言的字典,本节叙述如何将这些字典转换成 dictd 可用的格式。

为阻止不受限制的颁发,Amazon 赠送的 Kindle 字典和它贩卖的大部分书籍一样,是带有 DRM 的,即使用你的设备特征(如序列号)对书籍进行加密。这样做使得一个设备上 DRM 书籍在别的设备上无法读取。我无意在此文中探讨 DRM 的利弊,「无法自由地转换格式了」的确是反对 DRM 的人士抨击 DRM 的理由之一。由于你已经有 DRM 的密钥(也就是你的 Kindle 序列号),你可以使用 DeDRM Tools 去除字典文件的 DRM。

请参考 DeDRM Tools 项目里的说明去除字典文件的 DRM,在此不再赘述。

去除 DRM 之后可使用 KindleUnpack 将字典文件从 MOBI 解包成 HTML。注意这个 HTML 文件只有一行,直接使用带语法高亮的文本编辑器打开的话很容易卡死,建立先按照词条添加换行再打开:

sed -i 's!</idx:entry>!\0\n!g' mobi7/book.html

以上命令会每个 </idx:entry> 闭合标签后面加一个换行符,以减轻文本编辑器的渲染压力。

不同的字典其 HTML 格式略有不同,但每个词条的基本结构都类似。依然以明星词汇 abandon 为例:

<idx:entry scriptable="yes">
  <idx:orth value="abandon">
    <idx:infl>
      <idx:iform name="" value="abandoned"/>
      <idx:iform name="" value="abandoning"/>
      <idx:iform name="" value="abandons"/>
    </idx:infl>
  </idx:orth>
  <h3>abandon</h3> [...]
</idx:entry>

可以看到,除了原形(orthographic form)abandon 本身,其屈折形式(infectional form)abandoned, abandoning, abandons 也被收录了,这就是为什么在 Kindle 里查询非原形的单词时也能找到正确的词条的原因。这样「多对一」的结构在 HTML 表达起来不麻烦,但是对于 DICT 的字典(计算机科学意味)来说还是「一对一」更加容易实现,因此我写了一个脚本,用 BeautifulSoup 和 lxml 解析 HTML 字典,把这些 iform 全部展开。请先安装 requirements.txt 中的依赖,然后运行:

python3 convert_dict.py --sep '\n\t' --expand-iforms mobi7/book.html dict.txt

输出的文件结构类似于:

abandon
    [...]
abandoned
    [...]
abandoning
    [...]
abandons
    [...]

词头顶格,释义换一行空一个 tab 开始直到行尾。这样的文件格式正是 dictfmt 所支持的格式之一:

dictfmt -f --utf8 --mime-header text/html -s "My First Dictionary" my_first_dict > dict.txt

上述命令将 dict.txt 文件中内容喂给 dictfmt,输出 my_first_dict.dictmy_first_dict.index 两个文件,分别是 dictd 的数据库和索引文件。

三、配置 dictd 服务器

以下是我使用的 /etc/dict/dictd.conf 配置文件,供参考:

# 可自定义显示的欢迎信息
global {
  site site.info
}

# 默认允许所有 IP 地址查询
access {
  allow *
}

# 指定字典数据库文件位置
database OALECD7 {
  data "/var/lib/dict/oalecd7-iform.dict"
  index "/var/lib/dict/oalecd7-iform.index"
}
database XDHY5 {
  data "/var/lib/dict/xdhy5-iform.dict"
  index "/var/lib/dict/xdhy5-iform.index"
}

注意,如果你的 DICT 服务器对公网开放的话,需要考虑以下问题:

  • 防火墙允许 2628/tcp 流量;
  • dictd 自带 rate limiting 功能,可以配置一下以免被 DoS / DDoS;
  • 扒掉 DRM 自用是一码事,放公网供全世界使用是另外一码事——字典的版权问题值得注意。

四、配置 dict 客户端

虽然文首演示了用 telnet 查字典,但正经地查询还是应该用支持 DICT 协议的客户端。

dict 命令行客户端

dict 客户端默认会读取 /etc/dict/dict.conf 获取服务器,里面内置了几个公网服务器,但我都不需要,可以删空,只留一个 localhost。

由于选用的字典释义是用 HTML 做的样式,所以 dict 返回的结果需要渲染一下才好看。ELinks 可以达到这个目的。我使用了如下函数以方便在终端中查询释义:

function de() {
  dict "$1" | elinks -dump -dump-color-mode 1 -dump-width 120 -no-numbering -no-references | less -RF
}

更新:对多义词更友好的输出:

function de() {
  dict "$1" | sed -r '/definitions? found$/d; s/^From /<hr>/' | elinks -dump -dump-color-mode 1 -dump-width 120 -no-numbering -no-references | less -RF
}

效果如图(由于明星词汇 abandon 的释义太长,我换了个别的词):

dict-terminal_2018-06-08_05-42-01

字典里各种彩色样式都是能正常显示的,效果非常不错。

其他 GUI 客户端

除了 dict 这个命令行客户端,也有别的支持 DICT 协议的字典软件,比如 GoldenDict。据说 GNOME 和 XFCE 里自带的字典也是支持的,不过我没有测试过。

在 GoldenDict 的「DICT Servers」字典源里添加 localhost 即可使用。不过目前 GoldenDict 里有个 bug,无法处理 dictd 服务器返回的 HTML,只能以纯文本形式显示,非常丑陋。一个 workaround 是用它的「Programs」字典源,添加 dict %GDWORD% 这条命令,然后将输出结果作为 HTML 解析。

本文地址: https://wzyboy.im/post/1237.html


欢迎留下评论。评论前,请先阅读《隐私声明》。