QQ 电话工具插件,提供拨打、接听、挂断、说话和状态查询,并把电话动作注册到 ChatLuna 原生工具与 chatluna-character 的回复工具字段。
给后来者:个人做“真电话”这条路基本走不通。
如果你想要的是“到点手机真的响铃,接起来还能和机器人实时说话”,请不要在这个方向上继续投入。OneBot / NapCat 没有运营商电话 API;AVSDK Hook 方案需要独立侧车、虚拟声卡和版本敏感的外部插件,个人维护成本高且 QQ 账号有风控风险;Twilio 不能拨打中国大陆,阿里云语音通知个人不能开通,个人最后只剩实体 SIM + GSM/VoLTE 网关这条硬件路线,成本和稳定性都不划算。
本仓库保留下来,是作为“语音消息模拟 + 外部 bridge 协议”的实现记录和踩坑说明,不是能打真电话的插件。若你只是想在 QQ 里和机器人语音聊天,
simulated模式够用;若你已经有自己的电话网关,可以按bridge协议接进来。
先说明结论,避免误解:
- OneBot 11 与 NapCat 的公开 API 只有私聊/群聊消息、语音消息、
get_record等能力,没有“拨打 QQ 电话”或“接听 QQ 电话”的接口。 - 社区插件 cortico-world-qq-better 也明确写了:OneBot / NapCat 无法真正接入运营商电话;它的默认方案是用 OneBot 语音消息模拟通话。
- 真正接近实时对讲的方案(如 MaiBot QQ 语音通话适配器)需要在 NapCat 侧安装独立插件、Hook QQ 的 AVSDK、配置 PulseAudio 虚拟声卡,属于版本敏感的外部集成,不是普通 Koishi 插件能独立完成的。
因此本插件提供两种 transport:
- simulated(默认,开箱可用)
用 QQ 语音消息模拟通话:机器人发送语音/文字开场,用户回复语音;插件负责 STT → 业务层 → TTS → 语音消息。不是运营商电话,也不会让手机响铃。 - bridge(可选,接真实语音桥)
把拨号/接听/挂断/音频转发到外部 HTTP 服务;外部服务可以接 NapCat AVSDK、PulseAudio、CABLE 虚拟声卡或其他电话网关。插件本身不包含 AVSDK Hook。
如果你需要“真机响铃、双方实时说话”,必须部署 bridge 侧车;本插件只提供协议和工具层。
simulated:拨打、接听、挂断、发送语音/文字、接收用户语音并交给 STTbridge:把通话动作转发给外部语音桥接服务- ChatLuna 原生工具
qq_phone chatluna-character回复工具字段qq_phone(数组,支持一次多个动作)- STT / TTS provider 注册接口,供其他 Koishi 插件接入
- 通话超时自动挂断、单次通话最长时长
- 允许用户/群白名单
- 通话状态查询与列表
cd <你的 Koishi 实例目录>
npm install ./koishi-plugin-qq-phone-0.1.0.tgzYarn 4:
corepack yarn add "koishi-plugin-qq-phone@file:./koishi-plugin-qq-phone-0.1.0.tgz"安装后启用 qq-phone,并确保 koishi-plugin-chatluna 已启用(如果要注册 ChatLuna 工具);要注册 Character 字段还需要 koishi-plugin-chatluna-character。
| 命令 | 说明 |
|---|---|
qqphone.dial <QQ号> |
拨打;默认使用 greeting 开场 |
qqphone.answer [callId] |
接听;不填则接听当前用户最近的通话 |
qqphone.hangup [callId] |
挂断 |
qqphone.say <文本> |
在当前通话中合成语音并发送 |
qqphone.status |
查看当前通话 |
qqphone.calls |
列出未结束的通话 |
dial 的 -g 选项可覆盖开场白:
qqphone.dial 10001 -g 喂,是我,方便说话吗?
| 字段 | 默认值 | 说明 |
|---|---|---|
transport |
simulated |
simulated 或 bridge |
bridgeUrl |
空 | bridge 服务地址,如 http://127.0.0.1:8787 |
bridgeToken |
空 | bridge Bearer Token |
greeting |
喂,是我。 |
拨通开场白 |
maxCallMinutes |
30 |
单次通话最长分钟数 |
ringTimeoutSeconds |
60 |
呼叫超时秒数 |
toolEnabled |
true |
注册 ChatLuna 原生工具 |
characterToolEnabled |
true |
注册 Character 回复字段 |
toolName |
qq_phone |
工具名 |
ttsProvider |
空 | 默认 TTS provider;空则不合成语音 |
sttProvider |
空 | 默认 STT provider;空则不识别语音 |
allowedUsers |
[] |
允许使用电话命令/工具的 QQ 号;空表示不限制 |
allowedGuilds |
[] |
允许使用电话命令的群号;空表示不限制 |
debug |
false |
调试日志 |
启用后注册名为 qq_phone 的原生工具,参数:
{
"action": "dial | answer | hangup | say | status",
"user_id": "目标 QQ 号,dial 时可用",
"call_id": "可选,指定通话",
"text": "say 的文本",
"greeting": "dial 的开场白"
}工具只在私聊中授权(authorization: session => !session.guildId),避免在群里误触电话。
注册 qq_phone 数组字段,模型可以在 character_reply 中一次填写多个动作:
{
"qq_phone": [
{ "action": "dial", "user_id": "10001" },
{ "action": "say", "text": "我到楼下了" }
]
}字段由 Character 的 registerReplyToolField 注入;render 会生成 <qq_phone ... /> 供下游 XML 解析。
其他 Koishi 插件可以这样接入:
ctx.on('ready', () => {
ctx.qqPhone.registerTTS('my-tts', {
async synthesize(text, { call }) {
// 返回 Buffer、文件路径、URL,或 { buffer, format } / { file } / { url }
return { buffer: await myTts(text), format: 'wav' }
},
})
ctx.qqPhone.registerSTT('my-stt', {
async transcribe(audio, { call }) {
// audio 可能是 { buffer, format } 或 { file }
return { text: await myStt(audio) }
},
})
})然后在插件配置里设置 ttsProvider: 'my-tts'、sttProvider: 'my-stt'。
接口约定:
- TTS:
synthesize(text, options) -> string | Buffer | { buffer, format } | { file } | { url } - STT:
transcribe(audio, options) -> string | { text } registerTTS/registerSTT返回 dispose 函数
simulated transport 会优先发送合成语音;没有 TTS 或合成失败时降级为文字提示。
当 transport: 'bridge' 时,插件向 bridgeUrl 发以下请求:
POST /calls
{ "to": "10001", "greeting": "喂,是我。" }
-> { "id": "bridge-call-id" }
POST /calls/:id/answer
POST /calls/:id/hangup
{ "reason": "hangup" }
POST /calls/:id/audio
{ "text": "要说的文本", "audio": "<base64 或 URL,可选>", "format": "wav" }
请求头可带 Authorization: Bearer <bridgeToken>。
bridge 侧车需要自己完成:
- 接 NapCat / QQ AVSDK 或系统音频桥
- 把对方音频转成 STT 可处理的格式
- 把 TTS 音频送回通话链路
- 处理来电、挂断、超时
本插件不包含 AVSDK Hook、PulseAudio 配置或虚拟声卡配置。
- 电话是打扰性操作,建议设置
allowedUsers,只允许自己或信任用户。 - 默认不限制
allowedUsers是为了本地测试;公开机器人应配置白名单。 toolEnabled的原生工具只对私聊授权;群聊中不会暴露。- bridge token 请使用独立密钥,不要复用其他服务 token。
simulated不是运营商电话,不会让手机响铃;它只是 QQ 语音消息会话。bridge需要外部侧车;没有侧车时拨号会失败。- 语音消息格式依赖协议端;NapCat 通常需要 SILK/AMR,插件只负责把 provider 返回的音频交给协议端,格式转换由 provider 或协议端完成。
- 没有内置 STT/TTS;需要接入其他插件或 HTTP 服务。
- 目前只支持私聊电话;群聊电话未实现。
- 没有真正的“来电振铃”事件;
simulated由机器人主动发起,用户回复语音即视为通话中。
npm test测试覆盖:
PhoneService拨号/接听/说话/挂断/STTsimulatedtransport 的文字降级与消息发送- ChatLuna 工具注册(mock
@langchain/core/tools) - Character 回复字段注册、invoke 与 render
真实 QQ 通话需要协议端/侧车支持,无法在单元测试中覆盖。
MIT