koishi-plugin-dcqq-bridge
v0.4.0
Published
Discord 频道与 QQ 群(OneBot)之间的消息转发:完整渲染 embed、换算时间码、@全体、防 ping、失败降级、可选的机器翻译与 EVE 术语表
Maintainers
Readme
koishi-plugin-dcqq-bridge
A Koishi plugin that bridges Discord channels and QQ groups (OneBot v11, e.g. LLBot): full embed rendering, Discord timestamp conversion, real @everyone → QQ @全体 mapping with safety checks, ping-safe relaying to Discord (allowed_mentions), reply mapping, retries with graceful fallback, backfill after gateway reconnects, and optional machine translation (any OpenAI-compatible API) with keyword/moderation filtering and an EVE Online glossary. Everything optional is off by default.
在 Discord 频道 和 QQ 群 之间转发消息(双向或单向)。
功能
- 一行一个桥:一个 Discord 频道 ↔ 一个 QQ 群,方向可选 双向 / Discord→QQ / QQ→Discord。两个机器人自动识别。
- Discord → QQ 完整渲染:直接读 Discord 网关的原始数据,不经过适配器的解析(适配器会把
<t:…>时间码、<>之间的文字吞掉)。- 用户、角色、频道提及显示成名字;自定义表情显示成
[名字];Markdown 标记去掉、文字保留。 - embed 的作者、标题、描述、所有字段、页脚、时间戳、图片全部转发。
- 时间码 9 种样式全部换算成设定的时区。
- 贴纸、转发的消息、投票、组件消息都有去处。
- 用户、角色、频道提及显示成名字;自定义表情显示成
- QQ → Discord:插件自己调用 Discord API。
- 默认用 webhook 显示 QQ 发送者的名字和头像。
- 每个请求都带
allowed_mentions: { parse: [] },QQ 用户手打<@ID>也 ping 不到任何人。 - 图片先下载再上传(QQ 图片地址会过期);文件、表情包、合并转发、小程序卡片都显示成文字,不会静默消失。
- 回复:两边互相回复转过来的消息时,尽量变成真正的回复;做不到时加一行
↪ 回复 名字:内容。 - @全体(默认关闭,按桥打开):只有 Discord 上真正的 @everyone / @here 才会在 QQ 上 @全体。
- 发送前检查机器人是不是管理员、今天还剩几次;支持冷却时间、每日上限、给人工管理员预留次数。
- 查询失败时一律不 @全体,改成在消息前加文字。
- 失败处理:
- 一张图下载失败只换成
[图片],其余照发。 - 文件太大被 Discord 拒收时,这一批文件换成占位文字再发一次;用户的原文一个字都不截,占位文字放不下时合成一句「[另有 N 个文件过大未发送]」或另发一条。
- 连接失败会重试;被 Discord 限流时整个队列一起等。
- 可能已经发出去的请求不重发,避免重复消息。
- 超长消息自动分段。
- 一张图下载失败只换成
- 断线补发:插件运行期间 Discord 网关断线、重新建立会话后,补发断线期间漏掉的消息(最多往回 6 小时、每个频道最多 200 条),前缀标
(补发),补发的消息不会 @全体。插件自己停用、重载或 Koishi 关机后再启动时不补发(避免把旧插件已经转过的消息再转一遍)。 - 屏蔽词:每个桥单独设置,检查正文、embed 和文件名。
- 自动翻译(默认关闭,按桥打开):英译中、中译英,原文和【机翻】译文在同一条消息里;支持任何 OpenAI 兼容接口,不预设服务商。翻译失败、超时、被过滤时只发原文。
- 过滤(默认关闭):关键词表,外加可选的 OpenAI 审核接口;命中就不附译文,原文照常转发。
- EVE 术语表(默认关闭):插件自带从 CCP 官方数据生成的物品、组别、星系、星域中英文名;可以加载自己的黑话表文件,黑话表和官方名称表也可以填网址、定时在线更新。星系写成
Jita(吉他),1DQ1-A这类代号星系永远不翻。 - 管理命令:查看状态、暂停(可以只暂停翻译)、恢复、重新读取词表、从旧插件导入配置。
安装
- 在 Koishi 控制台的插件市场搜索
dcqq-bridge并安装。 - 需要先装好并启用:数据库插件(例如
database-sqlite)、Discord 适配器(@satorijs/adapter-discord)、OneBot 适配器(koishi-plugin-adapter-onebot)。 - Discord 开发者后台里给机器人打开 Message Content Intent,否则收到的消息是空的。
- 在要转发的 Discord 频道里,给机器人「查看频道」「发送消息」「附加文件」「管理 Webhook」权限。
配置
所有配置都在 Koishi 控制台的插件配置页里改,保存后立即生效。某一项写错时,只会影响那一项,不会让整个插件停掉:写错的桥会被跳过,并在 bridge.status 里标出来。
基本
| 配置项 | 默认 | 说明 |
|---|---|---|
| discordSelfId | 空 | 用哪个 Discord 机器人。只有一个时留空 |
| qqSelfId | 空 | 用哪个 QQ 机器人。只有一个时留空 |
| timezone | Asia/Shanghai | Discord 时间码换算到哪个时区。写错时改用 UTC |
| discordAsWebhook | 开 | 发到 Discord 时用 webhook 显示 QQ 发送者的名字和头像。关掉后由机器人发,前面加 [桥名 - 名字]。webhook 用不了时也会自动改由机器人发 |
| keepDays | 7 | 回复对应表保留几天 |
| authority | 4 | 管理命令需要的 Koishi 权限等级 |
| qqReorderMs | 0 | QQ 带回复的消息可能比后面的消息晚一点到(适配器要先查被回复的消息)。填 1000 左右时,每条 QQ 消息先等这么久,按 QQ 的消息序号排好再转发。0 = 不等待 |
| maxQueueAgeMinutes | 15 | 消息在队列里等太久(例如 Discord 长时间连不上)就丢掉不发,单位分钟;0 = 不限制 |
桥(表格,一行一个)
| 列 | 默认 | 说明 |
|---|---|---|
| 桥名 | 空 | 显示在前缀里,可以空 |
| Discord 频道 ID | 空 | 纯数字 |
| QQ 群号 | 空 | 纯数字 |
| 方向 | 双向 | 双向 / Discord → QQ / QQ → Discord |
| 启用 | 开 | |
| @全体 | 关 | 只对 Discord → QQ 方向有效 |
| 屏蔽词 | 空 | 每一条是一个正则表达式(普通的词直接写),不区分大小写,多条用 ;; 分隔。命中就不转发这条消息 |
| 翻译 | 关 | 还要打开下面「翻译」里的总开关才生效 |
- 一个 Discord 频道要发往两个 QQ 群,就写两行。
- 同一对频道和群写了两行、方向正好相反时,两行都照常工作,
bridge.status会建议合成一行「双向」。 - 直接编辑
koishi.yml时,ID 要加引号(discord: '123…'),否则会被当成数字并丢掉精度。 - 某一行的方向、启用、@全体、翻译写成了别的值(例如
D2Q、no)时,这一行整行无效,其他行照常工作。无效的行(包括 ID 写错、重复的行)会在bridge.status的最上面提示「第 N 行桥……已跳过(到控制台表格里检查第 N 行)」,在群里发、私聊不加-a时也能看到。
@全体
| 配置项 | 默认 | 说明 |
|---|---|---|
| fallbackText | 【全体通知】 | 没能 @全体 时加在消息最前面的文字 |
| reserve | 0 | 给人工管理员留几次:群里当天剩余次数不超过这个数时,插件不再 @全体 |
| dailyCap | 0 | 每个 QQ 群每天插件最多用几次,0 = 不另外限制 |
| cooldownMinutes | 0 | 同一个 QQ 群两次 @全体 之间至少隔几分钟 |
| maxAgeMinutes | 10 | Discord 消息发出超过这么多分钟就不再 @全体 |
@全体 只在下面这些条件全部满足时才会发生:
- 这个桥打开了 @全体,方向包含 Discord → QQ;
- Discord 上是真正 ping 了所有人的 @everyone 或 @here(只写了文字、没有权限 ping 的不算;「静默」发送的也不算;角色 ping 永远只显示成文字);
- 不是补发的旧消息;
- 没有在冷却中,也没到每日上限;
- 机器人在这个 QQ 群里是群主或管理员;
- QQ 显示还有剩余次数,并且剩余次数大于
reserve。
上面这些查询最多等 15 秒,并且最晚到这条消息 60 秒发送期限之前 10 秒;查不完就按「查询失败」改发带 fallbackText 的文字,保证文字版能发出去。
翻译
| 配置项 | 默认 | 说明 |
|---|---|---|
| enabled | 关 | 总开关 |
| baseURL | 空 | OpenAI 兼容接口的地址(见下表) |
| apiKey | 空 | API key |
| model | 空 | 模型名。推荐不带推理(思考)的模型,例如 gpt-4.1-mini、gpt-4o-mini |
| label | 【机翻】 | 译文前的标注。不能为空,为空时自动用「【机翻】」 |
| timeoutMs | 6000 | 翻译请求最多等多少毫秒,超过就只发原文 |
| maxPerHour | 0 | 每小时最多请求几次,0 = 不限 |
| extraBody | 空 | 可选:额外合并进请求体的字段,写成 JSON 对象(见下) |
| notCommands | help | 这些词开头的消息不当作命令,照常翻译;多个用 ;; 分隔,不区分大小写 |
| keepValueLabels | 空 | 这些标签后面的内容原样保留、不翻译;多个用 ;; 分隔,不区分大小写(见下) |
| context | 空 | 可选:给模型的背景说明,多行文字,原样加在系统提示词的最后(见下) |
| protectMemberNames | 开 | 中译英时,QQ 群成员名片里的名字原样保留、不翻译(见下) |
总开关打开但没填 baseURL 或 model 时,翻译按关闭处理,bridge.status 会显示原因。
服务商示例(只是例子,插件不预设任何一家;地址和模型名以各家官方文档为准):
| 服务商 | baseURL | model 例子 |
|---|---|---|
| OpenAI | https://api.openai.com/v1 | gpt-4.1-mini、gpt-4o-mini |
| DeepSeek | https://api.deepseek.com/v1 | deepseek-chat |
| 通义千问 | https://dashscope.aliyuncs.com/compatible-mode/v1 | qwen-plus |
模型选择:聊天转发要的是快,推荐不带推理(思考)的模型,例如 gpt-4.1-mini、gpt-4o-mini。o 系列、R1 这类推理模型要先「想」一阵才出译文,常常超过 timeoutMs,结果只发原文。
extraBody:服务商需要额外参数时用,写成一个 JSON 对象,原样合并进请求体,例如 {"max_tokens": 1000}。model 和 messages 由插件决定,写了也会被忽略。JSON 写错或不是对象(例如数组)时整项忽略,并在 bridge.status 里提示。
keepValueLabels:人名、舰队名、语音频道名被翻译或音译后,成员就没法在游戏里搜到了。某一行以「标签 + 冒号(: 或 :)」开头、并且标签在这个列表里时,冒号后面到行尾的内容不发给模型,译文里原样放回。标签前后可以有空格,也可以是 Discord 粗体(**FC Name:** 某人、**FC Name**: 某人 都算),行首可以有列表符号 -;标签不在行首的不算。这条规则只管像 ping 的消息:同一条消息里至少有 2 行以这些标签开头,或者冒号后面的内容很短(不超过 5 个词、40 个字符;英文按空格分词,一个汉字算一个词)。所以 FC Name: 某人 单独一行也保护,FC: everyone align to the sun and wait for warp 这种普通聊天照常翻译。Discord embed 的字段名在列表里时,不管长短,整个字段值都不翻译。AA 舰队 ping 的参考写法:
FC;;FC Name;;Fleet Commander;;Fleet Name;;Comms;;Formup Locationcontext:告诉模型这是什么场景的聊天、常见说法是什么意思。不为空时原样加在系统提示词的最后,英译中、中译英都加。一个字的黑话(例如「怪」「洞」)进不了术语表,靠这里说明。改了以后翻译缓存自动失效,同一句话会重新翻译。插件本身不内置任何游戏的内容,EVE 的参考写法(复制进配置,按需要增删):
这是 EVE Online 玩家之间的聊天和舰队通知。常见说法:怪 = NPC 海盗(rats),刷怪 = ratting;洞 = 虫洞;扫 = 扫描;蛋 = 太空舱(pod);军团 = corp;旗舰 = capital;远征 = escalation;交易语境里「收」= 买(WTB)、「出」= 卖(WTS);刷怪语境里的「6/10」「10/10」是异常空间的等级;「in 5」「bridge in 5」这类说法里的数字一般指分钟。背景说明会随每次翻译请求一起发给服务商,算进用量;写得越长,每次请求越贵。
建筑通知里的名字:Alliance Auth 的 Structures 插件发到 Discord 的建筑通知里,建筑名、军团名、联盟名翻译以后成员就没法在游戏里搜到了。翻译时(只影响发给模型的文字,转发的原文不变):
- 文字里有
https://evemaps.dotlan.net/corp/<名字>或…/alliance/<名字>这样的链接时,把链接里的名字还原(_换成空格,URL 解码),文字里原样出现的这个名字和网址一样换成占位符,例如belonging to Example Corp https://evemaps.dotlan.net/corp/Example_Corp里的Example Corp; The <建筑类型> <建筑名> in <星系>这种句式里,建筑类型是官方名称表里的建筑(Astrahus、Fortizar、Keepstar、Ansiblex Jump Bridge 等)时,中间的建筑名换成占位符,例如The Astrahus Example Keep in Jita里的Example Keep。The Astrahus in Jita is reinforced没有名字,照常翻译。建筑类型从插件自带的官方名称表取,不打开「EVE 官方名称表」也有效。
protectMemberNames:QQ 里直接写群友的名字(不是 @)时,名字容易被翻译或音译。打开后(默认开),中译英时从这个 QQ 群的成员名片(没有名片时用 QQ 昵称)里取名字,在原文里出现的原样保留。名片按常见写法拆开:去掉 []、【】 里的简称,按 -、_、|、/、空格分段,每段单独算一个名字;只收 3 个字及以上、含汉字的段(2 个字的太容易和普通词重合,例如「咖啡」),和术语表原文相同的段也不收。例如名片 [ABC]Some Pilot-小鱼干,「把小鱼干搞大」里的「小鱼干」不发给模型。成员名片每个群缓存 1 小时,在后台更新,不会让转发等待;插件刚启动后的头几条消息可能还没有名片(照常翻译),查询失败时也照常翻译。
另外,提示词里要求模型不翻译、不音译玩家和角色名、军团和联盟名及其简称、舰队制式名、语音频道名;这只是要求,模型不一定每次都照做,固定格式的 ping 请用上面的标签。
同时进行的翻译请求最多 4 个,其余排队;排队的时间也算在 timeoutMs 里,等不到就按超时处理(不发请求),只发原文。
必须用服务商的 API key。Claude、ChatGPT 这类聊天订阅不能拿来当机器人的后端。
哪些不翻译:
- 太短的(例如
gg、@张三 ok); - 已经是目标语言的;
- Koishi 命令(例如查价命令):第一个词(去掉命令前缀后)是本机的命令、不在
notCommands里,并且整条消息按空格分不超过 3 段。所以jita plex不翻译,Help needed in Jita, we are tackled照常翻译。只用来分组、自己没有功能的命令(例如bridge.status的上一级bridge)不算,所以bridge is up照常翻译; - 命中关键词的;
- 去掉网址、提及、术语等之后没有文字的(只有术语时,能直接换成目标语言的术语就本地生成译文,不请求服务商);
- 译文和原文一样的(不区分大小写和空白)不附;
- 断线后补发的消息。
不会发给翻译服务商的内容:用户名、QQ 号、群号、频道名、文件名。提及、@everyone、@here、网址、时间、代号星系、ISK 数字、keepValueLabels 标签后面的内容、建筑通知里的建筑名和军团名、群友名片里的名字会先换成占位符,翻译后再换回来。
embed 和术语的排版:embed 的字段在翻译时和转发的原文一样,一个字段一行「字段名:值」。英译中时术语表换进去的中文,挨着汉字或中文标点的一侧不留空格(例如「当心,墩子在我们的本星系」);两个中文术语之间只隔着空格、并且挨着空格的两个字都是汉字时,空格也去掉(「长须鲸级被抓」),有一边不是汉字时保留(「FRT 舰队」)。中译英时换进去的英文紧挨着英文字母或数字时补一个空格(「AAA燃料块」→ AAA Fuel Block),后面是复数词尾 s、es 时不补(Fuel Blocks)。
译文里的网址:网址换回来以后,后面紧跟的不是空格或行尾(英文标点除外)时补一个空格,前面紧挨着汉字或全角标点时也补一个空格,免得 QQ 把旁边的汉字、「)」也算进链接。译文每行末尾的空白会去掉。
图片的位置:发到 QQ 的消息一条放得下时,顺序是「原文 → 图片 → 译文」;太长要分几条发时,是「原文 → 译文」,图片跟在最后一条。Discord 消息里只有空白的行(例如 AA ping 里用来空一行的 ** **)转发后是空行。
零宽字符:有的 ping 工具会在消息里插入大量看不见的零宽字符(U+200B、U+200C、U+200D、U+2060、U+FEFF),Discord → QQ 转发和翻译之前都会删掉,免得术语匹配不上。组合 emoji(例如 👨👩👧)里夹在两个 emoji 之间的 U+200D 保留。
过滤
| 配置项 | 默认 | 说明 |
|---|---|---|
| keywords | 空 | 关键词,一行一个,不区分大小写。re: 开头的按正则 |
| keywordFile | 空 | 可选的关键词文件(相对 Koishi 实例目录),格式同上,# 开头是注释 |
| moderation | 关 | 用 OpenAI 审核接口检查译文 |
| moderationBaseURL | https://api.openai.com/v1 | 审核接口地址 |
| moderationApiKey | 空 | 审核接口的 key。留空时,只有翻译接口地址和审核接口地址完全相同(协议、主机、端口、路径,末尾的 / 不算)才借用翻译的 key;插件绝不会把别家的 key 发给 OpenAI |
- 原文命中关键词:不翻译;译文命中关键词:不附译文。原文照常转发。
- 英文关键词建议写成
re:\bword\b,否则ass会命中class。 - 审核接口管暴力、仇恨、色情这类内容,不懂中国的政治敏感词,那部分只能靠关键词表。
- 审核出错、超时或没有可用的 key 时,译文一律不附(宁可不翻,不发出没经过检查的译文)。
- 仓库里不附任何关键词表,由使用者自己维护。
术语表
| 配置项 | 默认 | 说明 |
|---|---|---|
| eve | 关 | 使用插件自带的 EVE 官方名称表(data/eve-glossary.json) |
| systemStyle | en(zh) | 有名字的星系、星域、星座在英译中时怎么写:Jita(吉他) / Jita / 吉他 |
| slangFile | 空 | 黑话表文件(YAML,相对 Koishi 实例目录);多个文件用 ;; 分隔,见下面「多个黑话表文件」 |
| officialUrl | 空 | 在线的官方名称表网址(JSON),\|\| 分隔备用网址;空 = 用插件自带的表。见下面「在线黑话表和官方名称表」 |
| refreshHours | 6 | 在线词表每隔几小时检查一次更新,最长 168(一周),填更大的按 168 算;0 = 只在插件启动和 bridge.reload 时检查 |
| overrides | 空 | 自己加的词条(表格),优先级最高(和群里 纠错 命令加的词条同级,同一个原文以这里为准) |
黑话表格式见插件自带的 data/eve-slang.example.yaml。每一条:
en、zh:标准写法;mode:keep原样保留 /force一定换成对应的词 /hint只作为参考交给模型(参考词条是否被采用由模型决定;想要固定的译法,请用 force);dir:both/en2zh/zh2en;en_aliases、zh_aliases:其他写法,匹配到时输出标准写法;category、note、confidence:只给人看。
黑话表写错时只在日志和 bridge.status 里提示,照常转发。改了文件后发送 bridge.reload 重新读取。
多个黑话表文件:slangFile 里可以写多个路径,用 ;; 分隔,例如公开的黑话表加上自己联盟专用的几条:
data/eve-cn-slang/glossary.yaml;;data/dcqq-bridge/local-slang.yaml- 按顺序加载,后面文件里同一个原文的词条覆盖前面的。「同一个原文」按方向看:英译中(
en2zh)比en(不区分大小写),中译英(zh2en)比zh;两条都是both时,en或zh相同都算。方向不同的两条只在共有的方向上比较,例如后面文件里的en2zh词条只覆盖前面的en2zh和both词条;前面的both词条只被覆盖了一个方向时,保留另一个方向(被en2zh覆盖后只剩中译英)。所以只改logistics → 后勤时,同样译成「后勤」的logi不受影响。同一个文件里的重复还是按原来的规则处理。 - 每个文件单独读:一个文件读不到或 YAML 写错,只在
bridge.status和日志里提示这个文件,其他文件照常加载。 bridge.reload的回复里按文件列出条数,例如「黑话表:glossary.yaml 301 条,local-slang.yaml 2 条」。- 只写一个路径时和以前完全一样。
- 可以直接用开源的中文黑话表:github.com/yilifaer/eve-cn-slang(
glossary.yaml),克隆或下载到 Koishi 实例目录下,把路径写进slangFile;也可以直接写网址,见下一节。
在线黑话表和官方名称表
黑话表和官方名称表可以放在 GitHub 等网站上,插件定时下载。改了数据仓库里的文件,各个机器人过几小时自己就用上新的,不用发新版插件、也不用每台机器手动更新。默认不开(网址都是空的)。
设置步骤(以 eve-cn-slang 为例,换成自己的仓库时把网址里的用户名、仓库名、分支名改掉):
打开插件配置的「术语表」,打开
eve(EVE 官方名称表)。slangFile填黑话表的网址;||后面是备用网址(GitHub 打不开时用 jsDelivr 的镜像)。还想加自己联盟专用的几条,就用;;再接一个本地文件,写在后面的覆盖前面的:https://raw.githubusercontent.com/yilifaer/eve-cn-slang/main/glossary.yaml || https://cdn.jsdelivr.net/gh/yilifaer/eve-cn-slang@main/glossary.yaml ;; data/dcqq-bridge/local-slang.yamlofficialUrl填官方名称表的网址,同样可以加备用网址:https://raw.githubusercontent.com/yilifaer/eve-cn-slang/main/official/eve-official.json || https://cdn.jsdelivr.net/gh/yilifaer/eve-cn-slang@main/official/eve-official.jsonrefreshHours默认 6 小时检查一次,一般不用改(最长 168 小时,也就是一周)。保存配置。私聊机器人发
bridge.reload,回复里应该有「glossary.yaml(在线)N 条,更新于 …」和「官方名称表:eve-official.json(在线)build N,N 条,更新于 …」。
规则:
slangFile里网址和本地文件可以混着写,按顺序合并,覆盖规则和多个本地文件完全一样(见上一节)。一项里用||分隔的网址按顺序试,第一个下载成功的为准。- 下载用 Koishi 的 HTTP 服务,每次最多等 30 秒;黑话表最大 5 MB,官方名称表最大 20 MB。服务器回复错误(404、500 等)时不读错误页的内容,直接算这个网址下载失败。支持 ETag / Last-Modified,文件没变时服务器只回一句「没变」,不重新下载整份。
- 内容没变就什么都不做;变了才重新加载术语表,翻译缓存随之失效(同一句话按新词条重新翻译)。
- 下载到的内容会先检查:黑话表必须是 YAML 列表,并且至少有一条能用的词条(拿到错误页、空文件、只有注释的文件、空列表就不用;本地黑话表文件不受这一条影响);官方名称表必须有
buildNumber和entries,每一条都有kind、en、zh,写了count时要和条数一样,条数至少是插件自带表的一半。检查不通过就继续用原来的版本,并在bridge.status里写原因。 - eve-cn-slang 的官方名称表已经自己写了
cat字段(区分舰船、建筑),插件直接用文件里的,所以「末日沙场」这种不带「级」的船名照样认得。只有整份表一条cat都没有时(例如自己做的、没写cat的表),插件才按英文名从自带的表补上。 - 在线官方名称表的 build 比插件自带的旧时(例如断网很久、只剩旧缓存,插件又升级了),用插件自带的。
bridge.status显示正在用的官方名称表(在线还是插件自带、build 号)和每个在线黑话表最近一次下载成功的时间;下载失败时写原因和时间。
断网或网站打不开时:
- 每次下载成功都存一份在 Koishi 实例目录的
data/dcqq-bridge/cache/下。插件启动时先用这份缓存,再在后台下载,启动和转发都不等网络。 - 下载失败就继续用手上的版本:这次运行里下载过的 → 缓存 → 都没有时官方名称表用插件自带的、在线黑话表当作读不到(其他黑话表文件照常)。连续失败只在日志里写一次,恢复时再写一次。
bridge.reload会马上把所有在线词表下载一次,并逐个报告,例如「glossary.yaml(在线)读不到(raw.githubusercontent.com 超时;cdn.jsdelivr.net 超时),用的是 10/05 08:00 (UTC+8) 的缓存,301 条」。
缓存只留一份:每个网址(按 || 前面的第一个网址算)只留一份内容和一个记录文件(ETag 等),新版本下载好后先写临时文件再替换旧的,不会越存越多。内容写进去了才更新记录;记录里存着内容的校验值,对不上(例如上次写到一半)时重新下载整份,不会把旧内容当成最新的。插件启动和 bridge.reload 时删掉已经不在配置里的网址的缓存、以及上次没写完留下的临时文件。改了第一个网址或者调换了 || 前后的顺序时,只要新旧网址里有一个相同,旧的缓存改成新的文件名接着用(断网时把镜像调到前面也不会丢),不算「不在配置里」。只删插件自己按固定格式命名的文件,缓存目录里别的文件不动。
版权:eve-cn-slang 的 official/ 目录(官方名称表)和插件自带的 data/eve-glossary.json 一样来自 CCP 的游戏数据,按 EVE 开发者许可协议使用(仅限非商业、非营利用途),不适用那个仓库的 CC BY 4.0;声明见该仓库的 official/NOTICE.md 和本插件的 data/NOTICE。
匹配规则:
- 最长的优先;
- 英文要求前后是词的边界,允许末尾多一个
s; - 只有 3 个字母以内的英文词(例如
FC、o7、x up)要求大小写完全一致; - 官方名称里是常用英语单词的(例如星域 Catch、Domain),只作参考,并且只在首字母大写、不在句首时匹配;
- 两个字以内的官方中文名(例如「吉他」)在中译英时只作参考;2 个字的组别、大类名(例如「建筑」「其他」「工具」)多是普通词,中译英时不用;
- 舰船名在中译英时也认不带「级」的写法(「末日沙场」= 末日沙场级 = Armageddon),只加去掉「级」后 3 个字及以上的;2 个字的(「灾难」「挑战」)多是普通词,不自动加,需要的话写进黑话表;和别的官方名称或黑话表词条重复时不加,以它们为准。
在群里直接纠错:看到翻错了,在桥接的 QQ 群或 Discord 频道里发 纠错 命令(需要 authority 级权限),马上生效,不用打开控制台,也不会让插件重载。
| 命令 | 作用 |
|---|---|
| 纠错 原文 = 译法 | 添加或修改一条词条,默认强制替换(force)。例如 纠错 standing fleet = 值守舰队,回复「已添加:standing fleet → 值守舰队(强制替换,英译中)」 |
| 纠错 -h 原文 = 译法 | 同上,但只作参考(hint),由模型决定用不用 |
| 纠错 -k 原文 | 原样保留,不翻译(keep) |
| 纠错 列表、纠错 列表 关键词 | 列出用命令加的词条(带关键词时只列包含它的);太长时分几条发 |
| 纠错 删除 原文 | 删除一条(两个方向都有时一起删) |
| 纠错 导出 | 把全部词条写成黑话表格式的 YAML,存到 Koishi 实例目录的 data/dcqq-bridge/fixes-<时间>.yaml,回复文件路径,方便以后并进黑话表 |
- 原文、译法里可以有空格;等号写全角
=也行。英文名是bridge.fix。 - 原文、译法被一对
<…>、〈…〉、《…》整个包住时,括号会被去掉:纠错 <afk cloaker> = <墩子>加的是「afk cloaker → 墩子」,纠错 删除 <afk cloaker>也能删掉它(沙盒这类会把<…>当成标签的平台也一样)。 - 方向自动判断:原文有英文字母、译法有汉字 → 英译中;原文有汉字、译法有英文字母 → 中译英;判断不出来(例如两边都是英文)就回复用法,不添加。
-k只看原文:有汉字 → 中译英(中英混写的词也算中文),只有英文字母 → 英译中。 - 同一个方向、同一个原文再加一次就覆盖旧的(3 个字母以上不分大小写)。
- 加的时候和黑话表走同样的检查:原文、译法至少 2 个字;3 个字母以内的英文只匹配大小写完全一致的写法;原文是常用英语单词、又是强制替换时,回复里会提醒「可能误伤普通句子」,但照样加上,不想要就改用
-h再加一次;用-k原样保留常用英语单词(例如纠错 -k fleet)时也会提醒「普通句子里的 fleet 也不会被翻译」,同样照样加上。 - 优先级和控制台的
overrides相同,高于黑话表和官方名称表。控制台overrides里有同一个原文时以控制台为准,纠错 列表里会标出来。 - 词条存在数据库里(表
dcqqbridge_glossary),不写进 koishi.yml。每次增删后翻译缓存都会失效,同一句话下次会按新词条重新翻译。 - 回复一条转发过来的消息再发纠错:加完词条后,插件从那条消息里取回原文(去掉开头的
[桥名 - 名字]和后面的译文),用新词条重新翻译一次,把新译文回复在你发命令的群或频道里(不转发到对面),方便马上确认效果。重新翻译失败不影响词条本身。 - 纠错命令和机器人的回复都不会转发到对面。
管理命令
所有命令都需要达到 authority 设置的权限等级。任何达到这个等级的人都能暂停、恢复、导入;如果别的插件也需要给人这个等级,考虑把本插件的 authority 调高。
| 命令 | 作用 |
|---|---|
| bridge.status [桥](桥接状态) | 每个桥的状态:启用、暂停或无效,最近一次转发时间,24 小时转发数和失败数,最近一次失败的原因。@全体 桥还显示今天用了几次、剩余次数(每次执行命令时实时向 QQ 查询,最多等 8 秒;查不到时写明原因,例如「查询失败:超时」「QQ 机器人不在线」)、改发文字的次数和原因。在群里只显示和这个群有关的桥;私聊时加 -a 连无效、未启用的行也显示 |
| bridge.pause [桥](桥接暂停) | 暂停。不带桥 = 全局暂停,所有转发立即停止。重启后仍然有效。加 -t 只暂停翻译,转发照常 |
| bridge.resume [桥](桥接恢复) | 恢复;加 -t 只恢复翻译 |
| bridge.reload | 重新读取关键词文件和黑话表;在线黑话表、官方名称表马上下载一次 |
| bridge.import | 从 @myrtus/forward 的配置生成桥(只能私聊使用,只输出、不改任何配置) |
| bridge.fix(纠错) | 在群里加、改、删术语词条,马上生效,见上面「术语表」一节 |
「桥」可以写成:bridge.status 里的编号、Discord频道ID:QQ群号、或桥名。
在桥接的群或频道里发的本插件命令(包括中文别名、带不带命令前缀、bridge status 这种空格写法)不会被转发到对面,机器人的回复也不转发;别的插件的命令照常转发。只有 bridge 一个词(例如「bridge is down」)不算命令,照常转发。
统计数字只保存在内存里,插件重载后清零(在控制台保存一次配置也算重载);bridge.status 第一行会写「统计从 … 重载后开始」。暂停状态保存在数据库里。
bridge.status 里的时间按 timezone 显示,后面标上时区,例如 09/30 12:38 (UTC+8);电脑本机的时区和它不同时不会看错。
给账号设权限(例如在 Discord 上也能用命令)
权限等级是 Koishi 自己管的,本插件不做特殊处理。QQ 号和 Discord 账号在 Koishi 里是两个不同的用户,QQ 号有 4 级权限,不代表 Discord 账号也有;新账号默认是 1 级。要在 Discord 频道里用 纠错、bridge.status 这些命令,需要给 Discord 账号单独设成 4 级(或者 authority 设置的等级)。
先让这个 Discord 账号在机器人能看到的频道里发一句话,Koishi 才会给它建用户记录。然后任选一种方法:
方法一:在控制台的「数据库」页面改(需要启用 dataview 插件,控制台左边栏叫「数据库」)
- 打开
binding表,找到platform是discord、pid是你的 Discord 用户 ID 的那一行,记下aid那一格的数字。(Discord 用户 ID:Discord 设置 → 高级 → 打开「开发者模式」,然后右键自己的头像 →「复制用户 ID」。) - 打开
user表,找到id等于刚才那个数字的一行,把authority改成4,保存。
方法二:用 authorize 命令(admin 插件提供)
- 由一个 5 级的账号发送:
authorize 4 -u @discord:你的Discord用户ID,例如authorize 4 -u @discord:123456789012345678(这里的数字是编造的)。 - Koishi 规定只能给别人设比自己低的等级,所以 4 级的账号不能用这个命令把别人设成 4 级;手头没有 5 级账号时用方法一。
改完后在 Discord 频道里发 bridge.status 试一下:有状态回复就说明设好了;权限还不够时 Koishi 会回复「权限不足。」(或者什么都不回)。
从 @myrtus/forward 迁移
- 私聊机器人发送
bridge.import,核对它给出的报告:生成了几个桥、跳过了哪些、为什么跳过,以及行为变化(例如屏蔽词现在不区分大小写、也检查 embed)。 - 把回复里的
bridges:配置粘进本插件的配置。这份配置也保存在 Koishi 实例目录的data/dcqq-bridge/import-<时间>.yaml。 - 在同一次保存里停用 @myrtus/forward、启用本插件。两个插件绝不能同时转发同一个频道,否则每条消息会发两次。
- 出问题时停用本插件、重新启用旧插件即可。旧插件的配置和数据表本插件都没动过。
常见问题
- QQ 机器人离线,转发不出去:OneBot 适配器用「正向 WebSocket」连接时,LLBot 重启或断开后不会自动重连。
bridge.status会显示「QQ 机器人离线」。解决办法:在 Koishi 控制台重载 onebot 适配器;或者把连接方式改成「反向 WebSocket」(由 LLBot 负责重连)。 - Discord → QQ 收不到:确认 Discord 开发者后台打开了 Message Content Intent,并且机器人在频道里有「查看频道」权限。
- QQ → Discord 显示的是机器人自己的名字:机器人没有「管理 Webhook」权限,插件已自动改由机器人发送。日志里每小时会提醒一次。
- QQ 带回复的消息顺序偶尔颠倒:见
qqReorderMs。
隐私与合规提示
以下不是法律意见。
- 为稳妥起见,译文都带「机器翻译」标注:《人工智能生成合成内容标识办法》2025-09-01 起施行,是否适用于群聊翻译存在争议,所以
label不能为空。 - DeepSeek 开放平台协议 §3.3 把 API 开发者视为服务提供者,§3.4 要求对输入和输出做关键词过滤,关键词表就是用来满足这一条的。打开翻译但关键词表为空时,插件会在启动时提醒。
- 个人信息:把 QQ 群消息发到境外(Discord;打开翻译后还有翻译服务商,开启审核时还有 OpenAI)可能涉及个人信息出境,需要告知群成员并取得单独同意。建议在群公告里列出所有接收方。
- 使用 OpenAI 时,注意它的「支持的国家和地区」条款。
- webhook 模式会把 QQ 头像地址显示在 Discord 上,这个地址里带有 QQ 号。
- QQ 的用户协议禁止第三方客户端和自动发送消息,机器人账号本身就有被限制或封禁的风险。消息越多、@全体 越频繁,风险越高。
开发
npm ci
npm run typecheck
npm test
npm run build测试用真实的 Koishi、Discord 适配器、OneBot 适配器,加上模拟的 Discord 服务器和模拟的 LLBot,不访问外网。实测步骤见 docs/测试步骤.md,决策记录见 DECISIONS.md。
数据来源与许可
代码使用 MIT 许可。以下两个数据文件不属于 MIT,详见 data/NOTICE:
data/eve-glossary.json:从 CCP 的 EVE Online 静态数据生成,按 EVE 开发者许可协议 使用(仅限非商业、非营利用途)。用npm run build:glossary可以从官方最新数据重新生成。© 2014 CCP hf. All rights reserved. "EVE", "EVE Online", "CCP", and all related logos and images are trademarks or registered trademarks of CCP hf.
在线官方名称表(
officialUrl,例如 eve-cn-slang 的official/eve-official.json)同样是 CCP 游戏数据的衍生物,许可和版权声明同上;下载后的缓存也一样。data/common-words.txt:常用英语单词表,取自 SCOWL(Spell Checker Oriented Word Lists,经 npm 包 wordlist-english)的第 10、20、35 级,按 SCOWL 的许可使用,版权声明原文见data/NOTICE。
致谢
- Koishi 和 Satori 适配器。
- 特别感谢 @myrtus/koishi-plugin-forward 的作者。我们的 Discord 频道和 QQ 群一直靠它互通,它是这个插件的起点。后来因为我们的游戏(EVE Online)有一些特殊需要,例如舰队通知里的 @全体、embed 排版、中英机翻和游戏术语,才另外写了这个插件。开发时阅读过它的代码来理解原来的行为,但没有复制它的任何代码(它是 AGPL-3.0 许可)。向原作者致敬!
