Skip to content

文档同步

本页由 plugins/adapters/icqq/README.md 自动生成。请修改包内 README 后运行 pnpm sync:adapter-docs。

@zhin.js/adapter-icqq ​

ICQQ Plugin Runtime 适配器 — 进程内直接使用 @icqqjs/icqq Client

功能特性 ​

  • 群聊 / 私聊 / 群临时会话 / QQ 频道消息
  • 入站:ICQQ Client 原始事件经 Endpoint.emit(...) 唯一入口上送;文本与图片 / 语音 / 视频 / 文件统一归一为 canonical Segment + MediaRef
  • 出站:canonical Segment 直接投影为 ICQQ 原生 Sendable,再由 sendGroupMsg / sendPrivateMsg / …发送
  • 群聊 reaction:control.addReaction / removeReaction(协议 ACK 失败不阻塞后续发送)
  • Agent 工具:按职责拆分在 agents/icqq/skills/icqq-*/tools/,只在对应 Skill 激活后披露
  • Console Endpoint 管理:src/endpoint.ts 显式实现 EndpointManagement(好友/群/群成员列表、请求审批、删好友、踢人、禁言、设管理)

安装 ​

bash
pnpm add @zhin.js/adapter-icqq @icqqjs/icqq
# 可选:未配置 signApiAddr 时走本地签名
pnpm add @icqqjs/qqsign

@icqqjs/icqq 是适配器的可选对等依赖(peerDependencies + optional)。应用侧需自行安装;monorepo 内另在 devDependencies 声明以便构建/测试。

@icqqjs/* 发布在 GitHub Packages,需:

ini
@icqqjs:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}

并设置具有 read:packages 权限的 NPM_TOKEN(CI 使用仓库 secret PERSONAL_TOKEN)。

签名:未配置 signApiAddr 时,若已安装 @icqqjs/qqsign 则自动走本地签名。

前置条件 ​

  1. 准备可登录的 QQ 账号,并选择远程 signApiAddr 或安装本地 @icqqjs/qqsign。
  2. 确保运行目录可持久化设备与登录状态;容器部署应挂载对应数据目录。
  3. 首次登录可能要求二维码、滑块或设备确认,可在 Console 登录待办或终端完成。

配置(Plugin Runtime) ​

yaml
plugins:
  icqq:
    master: "1659488338"        # 必填,顶层共享(/approve 与 master 角色)
    autoReconnect: true
    endpoints:
      - id: "${ICQQ_ACCOUNT}"   # QQ 号
        # password: "${ICQQ_PASSWORD}"  # 可选;不填则扫码登录

多账号:一个插件实例挂多个 endpoint(endpoints 数组逐项覆盖顶层字段,id 必填):

yaml
plugins:
  icqq:
    master: "1659488338"
    endpoints:
      - id: "${ICQQ_ACCOUNT}"
      - id: "${ICQQ_ACCOUNT_2}"
      - id: "${ICQQ_ACCOUNT_3}"

AdapterIndex 会先合并插件实例默认值与 endpoint 覆盖值,再把一份完整配置交给 ICQQ adapter。协议层只接受这份展开后的 endpoint 配置,不读取环境变量,也不再次解析 嵌套的 endpoints。环境变量替换由 composition root 在配置加载阶段完成。

Send conversation ​

类型conversation
私聊{ kind: 'private', id: uin }
群聊{ kind: 'group', id: gid }
群临时会话{ kind: 'private', id: uin, parent: { kind: 'group', id: gid } }
频道{ kind: 'channel', id: channelId, parent: { kind: 'channel', id: guildId } }

直接使用完整 ICQQ Client ​

适配器把 icqq 注册为 @icqqjs/icqq.Client 与 SDK EventMap 的类型判别项。 所有 Feature 都使用同一个 adapter 字段完成类型缩窄与运行时过滤:

ts
import { defineHandler } from 'zhin.js/handler'
import '@zhin.js/adapter-icqq'

export default defineHandler({
  adapter: 'icqq',
  event: 'request.group.add',
  async handle({ client, event }) {
    await client.setGroupAddRequest(event.flag, true)
  },
})

client 就是当前账号的真实 ICQQ SDK 实例,不是 Endpoint facade。未知扩展事件可显式使用 event: '*';此时 Client 类型保持完整,事件 payload 为 unknown。普通消息回复仍应走 Message.$reply;群管理、请求审批与 ICQQ 专属查询可直接调用当前事件的 client。

Command context 的 $client 是按需读取的 getter:

ts
export default defineCommand({
  adapter: 'icqq',
  async execute(context) {
    await context.$client.sendLike(Number(context.sender!.id), 10)
    return 'ok'
  },
})

Middleware 使用同一个判别字段;不声明 adapter 时 $client 的类型是 unknown:

ts
export default defineMiddleware<Message>({
  target: 'inbound',
  adapter: 'icqq',
  async handle(context, next) {
    console.log(context.$client.uin, context.input.content)
    await next()
  },
})

Agent tool 的 IM turn 同样直接读取当前 Client:

ts
export default defineAgentTool({
  adapter: 'icqq',
  description: '获取群列表',
  async execute(_input, context) {
    return context.$client.getGroupList()
  },
})

adapter 缺失时 $client 静态类型为 unknown;声明后若实际 operation 不属于 ICQQ, 运行时会在执行前拒绝。Client 只能在当前 operation 内使用。task、schedule 或 Host 等脱离 IM turn 的场景,才使用 icqqClient.get(context, endpointId) 显式选择账号。

架构 ​

  • plugin.ts + adapters/icqq/index.ts:Plugin Runtime 入口与 defineAdapter 声明
  • src/endpoint.ts:组合 ICQQ Client,只协调账号 transport、Zhin lifecycle 与各能力端口
  • src/content-resolver.ts:保存已观察消息,并按深度和条数限制递归展开合并转发
  • src/icqq-inbound.ts:把 ICQQ 原生消息归一为 Zhin 入站消息
  • src/protocol.ts:配置解析、会话映射与出站目标转换
  • src/client.ts:向插件作者暴露 ICQQ Client / EventMap 类型注册
  • Agent 工具:agents/icqq/skills/icqq-*/tools/<name>/index.ts;权限说明见 PERMITS.md

阅读适配器实现时从 endpoint.ts 看能力装配,再进入对应能力文件。包外代码只从 @zhin.js/adapter-icqq 与 zhin.js/adapter 的公开入口导入,不依赖上述源码路径。

Plugin Runtime 迁移说明 ​

  • 不再经过 @icqqjs/cli IPC 守护进程;登录态与协议栈都在本进程。
  • autoReconnect:Client 断线后按配置自动重连(stop() 为主动断开,不触发重连)。
  • outboundMedia: file | base64:file 在发送期间把 segment base64 物化为临时文件并于发送结束清理;base64 使用 ICQQ 原生支持的 base64:// file 参数。
  • ICQQ 的语音、视频、文件是独立消息元素;它们与其他段混发时适配器会明确拒绝,避免协议栈静默丢段。
  • 入站语音 / 视频在 Endpoint 持有原生 Client 时解析可下载 URL;解析失败则保留真实平台引用,不伪造或丢弃媒体。
  • Console 社交/群管 RPC 已接线:endpoint 把好友/群/群成员列表、请求审批和群管操作归一化为冻结的 EndpointManagement。Host 只消费该语义端口。
  • 好友/入群请求与通知:经 the unified Endpoint.emit(...) ingress 分发到 handlers(notice.receive / request.receive);审批走 Request.$approve / EndpointManagement.approveRequest。Console request.list 优先读 management.listRequests()(getSystemMsg),不再写入 unified_inbox_request/notice。
  • system.*(登录扫码等)分发到 system.receive。
  • 登录辅助:system.login.qrcode|slider|device|auth 经 loginAssistToken(LoginAssist)挂起待办;刷新后可用 Console login.list / login.submit 或终端 stdin 继续(对齐 icqq 官方 stdin 流程)。system.online / login.error 会清理该 endpoint 待办。

故障排查 ​

现象排查
一直停在登录中打开 Console 登录待办,完成二维码、滑块或设备确认
签名失败检查 signApiAddr;本地模式确认 @icqqjs/qqsign 已安装且版本匹配
重启后重复登录持久化设备与会话数据目录,避免每次生成新设备
请求或通知未显示在 Endpoint 详情检查请求视图;ICQQ 优先读取平台请求列表

License ​

MIT