Skip to content

Repository files navigation

Notify-Bridge-Bot

一个基于 napcat-sdk 的 QQ 私聊客服催回复机器人。

它的目标很简单:客户发来私聊消息后,如果客服迟迟没有回复,机器人就把消息整理后推送到内部群里提醒;如果超时更久,再按里程碑继续催办;客服处理完成后,再自动结束监控并存档会话。

当前仓库已经实现了完整的消息监听、好友申请自动处理、群内快捷指令、夜间免打扰、状态持久化与会话归档。本文档基于当前代码现状整理,尽量保留原有使用视角。

适用场景

  • QQ 私聊作为客服入口
  • 需要把未回复客户集中提醒到内部群
  • 希望减少人工盯聊天窗口的成本
  • 希望通过群内引用命令、贴表情、戳一戳来快速处理会话
  • 希望保留客服响应耗时和会话记录,方便追踪

功能概览

  • 自动通过好友申请,并在内部群发送通过通知
  • 好友通过后延迟发送欢迎消息,支持纯文本或结构化消息段
  • 监听非白名单用户的私聊消息
  • 对新消息做 1 分钟防抖,避免客户连续补充消息时频繁提醒
  • 将待处理客户最近消息整理为合并转发并发送到内部群
  • 提醒时可随机 @ 当前排班内可用成员;若当前无人值班,则直接发送提醒
  • 按里程碑进行超时催办
  • 支持夜间免打扰:夜间延后提醒,次日指定时间汇总发送
  • 支持在通知群引用机器人消息后执行 .say / .more / .bye / .close / .list / .help
  • 支持通过配置的表情映射触发 say / more / bye / close / cancel / recall
  • 支持限时撤回:.say 发送成功后,点击通报消息上的撤回表情即可撤回刚发送给客户的私聊消息
  • 支持 AI 回复建议:提醒合并转发的第一层附上根据客户最近对话生成的建议答复(OpenAI 兼容接口,可选图片输入,可在线开关)
  • 支持 .say 两段式回复:先输入 .say,再发送下一条群消息作为要转发给客户的内容
  • 支持客服直接私聊回复客户后自动结束会话
  • 支持内部群戳一戳查看运行状态面板
  • 支持状态持久化:程序重启后自动恢复待回复客户、监听消息和夜间延后通知
  • 支持会话归档:对话周期内事件实时写入本地 SQLite 会话库,会话关闭后再按时间段拉取历史对账补漏,双保险保证记录完整
  • 支持统计已完结会话的最短、最长、平均和中位回复耗时
  • 支持临时静音开关:群内 .mute(无参数不限时)/ .unmute,或本地 Shell 控制台开启;静音期间提醒暂存,解除后汇总发出;shell unmute 与群内 .unmute 行为一致
  • 支持本地终端 Shell 控制台:与 bot 同进程、同生命周期绑定;命令回执仅本地;日志插入时自动重绘 prompt,避免打断输入
  • 终端支持会话处理:say / bye / close(可用客户 QQ 或 all=当前待回复队列);终端不提供 more
  • 群内会话命令在引用消息之外,也支持用客户 QQ 或 all 直接操作:.say <QQ|all> … / .bye <QQ|all> / .close <QQ|all> / .more <QQ|all>(带 Reply 时仍以引用为准;all=待回复队列)

工作流程

  1. 客户发送私聊消息,机器人将其加入待回复队列。
  2. 如果客户在 1 分钟内继续发消息,计时会重置,并合并到同一轮待处理会话。
  3. 超过 1 分钟仍未回复时,机器人向内部群发送“新客户提醒”。
  4. 机器人会根据 milestones 配置继续发送“超时催办”。
  5. 客服可以直接私聊回复客户,也可以在内部群引用消息执行命令,或使用配置好的表情快捷处理。
  6. 会话结束后,客户会从待回复队列中移除,回复耗时会计入统计;会话周期随即关闭,并按周期时间段拉取历史消息对账补漏。
  7. 若开启夜间模式,夜间时段的新客户提醒和里程碑提醒会暂存,并在次日指定时间汇总发送。

内部群命令

带引用时,除 .list 和 .help 外,其余会话命令都应当 引用(回复) 一条由机器人发送、且仍在监听窗口内的消息。通常引用的是合并转发消息,也可以是机器人发出的操作反馈消息。
也可不引用:对 .say / .bye / .close / .more 使用 .命令 <客户QQ|all> … 直接指定客户;带 Reply 时始终以引用解析目标。

指令 用途 示例 是否需要引用
.say <内容> 向客户发送私聊消息 .say 您好,请问有什么可以帮您? 是
.say 进入“等待发送内容”状态,触发者的下一条群消息会被转发给客户 .say 是
.say <QQ|all> <内容> 不引用消息时,按客户 QQ 或 all(待回复队列)私聊发送 .say 12345678 您好 / .say all 请稍候 否(用 QQ/all)
.say <QQ> 不引用时进入等待输入,下一条群消息发给该 QQ(all 不支持等待输入) .say 12345678 否(用 QQ)
.more 获取客户最近 100 条历史消息,并以合并转发形式返回 .more 是
.more <QQ> 不引用时,按客户 QQ 拉取历史合并转发;不支持 .more all;无参且队列仅 1 人时自动匹配 .more 12345678 否(用 QQ)
.bye 向客户发送结束语并关闭会话 .bye 是/否(无参且队列仅 1 人时自动匹配)
.bye <QQ|all> 不引用时,按客户 QQ 或队列全部发送结束语并关闭 .bye all 否(用 QQ/all)
.close 关闭会话,但不发送结束语 .close 是/否(无参自动匹配同上)
.close <QQ|all> 不引用时,按客户 QQ 或队列全部关闭会话 .close all 否(用 QQ/all)
.list 列出当前待回复客户及等待时长 .list 否
.status 发送运行状态面板(与戳一戳机器人相同) .status 否
.mute [分钟] 临时静音(不带参数不限时;.mute 30 为 30 分钟) .mute 否
.unmute 解除静音并汇总发出延后提醒(与终端 unmute 一致) .unmute 否
.reload 重载当前明文配置(兼容 .reload cfg) .reload 否
.help 显示帮助信息 .help 否

补充说明:

  • 目标解析优先级: 带 Reply 时始终走引用路径。无引用时:显式 QQ → all(.more 不允许 all)→ 无参且待回复队列恰好 1 人则自动匹配该客户(人多/为空则提示指定 QQ)。
  • all = 当前待回复队列(不是全部好友);.say all 必须附带内容。
  • 表情快捷操作: 多客户合并转发上,say/bye/close 作用域扩展为该消息内全部客户;more 仅单客户,多客户时提示手动处理。
  • 注意: 表情 more 只支持单客户;多客户合并转发请用群命令 .more <QQ>。
  • 命令识别按前缀匹配,不是完整 token 精确匹配。 实现上运维类命令用 startswith 判断(见 src/group_msg.py 的 OPS_COMMAND_PREFIXES),因此 .statusfoo 会进 .status 分支并发送状态面板,.unmute-now 会执行解除静音;.mute 后跟非法参数(如 .muted)会按参数解析失败提示,而不是忽略整条消息。请严格使用上表中的命令写法,只附带明确支持的参数(如 .mute 30、.reload cfg),不要在命令后拼接后缀或自定义后缀。若后续改为分词精确匹配,以代码为准。
  • .say 转发时会去掉群消息中的 Reply 段,其余消息段会原样发送给客户,因此文本、图片等消息段都可用。
  • .say 进入等待状态后,机器人会给提示消息贴上 cancel 表情;贴该表情即可取消本次发送。
  • .say 发送成功后,机器人会在通报消息上贴上 recall 撤回表情;在 recall_window_seconds(默认 60 秒)内点击该表情即可撤回刚发送给客户的私聊消息,超时后点击会提示无法撤回。
  • .list 最多展示前 20 条待回复客户记录。
  • 如果引用的是已过期或无法识别的机器人消息,机器人会返回“操作已过期”或“无法识别的消息”。

表情快捷操作

机器人会自动给可操作的消息贴上预设表情,群成员可以直接点表情触发动作。表情与命令的对应关系由 emoji_mapping 控制。

默认示例:

动作 默认 QQ 表情 ID 说明
say 123 进入发送消息流程
more 289 拉取最近历史消息
bye 124 发送结束语并关闭会话
close 75 直接关闭会话
cancel 96 取消等待中的 .say
recall 89 限时撤回 .say 刚发送给客户的私聊消息
  • 注意: 表情快捷操作中 more 只支持 单客户 消息;say/bye/close 在多客户合并转发上作用域为该消息内全部客户。
  • recall 表情只在 .say 的成功通报消息上出现;在 recall_window_seconds 时间窗口内点击才会生效,撤回成功后机器人会在群内提示。

私聊交互

  • 客户发消息后自动进入队列(白名单用户除外)。
  • 若客户刚通过好友申请,在 processed_friend_requests_expire 窗口期内发来的消息会被忽略,避免欢迎阶段误入队列。
  • 客服直接私聊回复客户,会话自动结束。
  • 客户在私聊窗口戳一戳机器人时,如果该客户当前在待回复队列中,机器人会发送结束语并结束会话。

夜间免打扰模式

在配置文件中可以设定夜间时段(例如 23:30 到 07:00)和次日汇总时间。夜间时段内:

  • 新客户提醒不会立刻发送,而是暂存到内存中的延后通知队列
  • 里程碑超时提醒也不会立刻发送,而是一起延后
  • 到达 summary_time 附近的 10 分钟窗口内,机器人会把夜间积累的客户汇总后统一发送
  • 汇总消息仍会带合并转发内容,方便客服次日直接处理

注意:

  • 夜间汇总按客户聚合,不区分“新客户提醒”还是“第几个里程碑提醒”。
  • 夜间延后通知会写入状态文件,因此程序重启后仍可恢复。

临时静音开关

用于会议、交接班等不想被提醒打扰、但又不想改夜间配置的场景。

  • 开启(不限时,默认):内部群 .mute 或终端 mute;直到手动解除
  • 开启(限时):.mute 30 / mute 30(单位分钟)
  • 解除并汇总:群内 .unmute 或终端 unmute(两者行为一致:解除静音 + 向通知群汇总发出延后提醒)
  • 命令回执:群命令回在群里;终端命令只打本地 stdout,不向群发送命令 ACK
  • 限时静音到期:巡检任务自动解除,并汇总发出延后提醒
  • 静音期间:新客户提醒与里程碑催办不会立刻推送到内部群,而是与夜间模式共用延后队列
  • 解除或到期:若当前不在夜间时段,则将延后通知按客户聚合后汇总发出;若仍在夜间,则继续留给次日夜间汇总
  • 状态:戳一戳机器人或 .status / 控制台 status 可查看当前是否静音及剩余时长
  • 持久化:mute_until 写入 state_file(0 未静音 / -1 不限时 / >0 截止时间戳),重启后仍有效

本地 Shell 控制台

bot 启动后会在同一进程读取终端 stdin。控制台与 bot 运行绑定:随 bot 启动,随 bot 优雅关停结束;控制台内 quit / exit / stop 会请求停止整个 bot(与 Ctrl+C 相同路径)。在 systemd 等无 stdin 环境下控制台会自动禁用,不影响 bot 主流程。

约束:终端命令的回执只写本地 stdout,不向任何 QQ 群发送命令 ACK。
unmute 除外——它与群内 .unmute 一致,会汇总把延后客户提醒发到通知群(业务消息,不是命令回执)。

日志与输入协调:运行中日志插入终端时,会清行打印日志并重绘 notifybot> 与已键入内容,避免命令被日志“打断”。

命令 用途
help 显示帮助
status 本地查看运行状态(含静音状态)
list 列出待回复客户
mute 不限时静音(直到 unmute)
mute <分钟> 限时静音
unmute 解除静音并汇总延后提醒(与群内 .unmute 一致)
reload 重载明文 config.yaml
say <qq|all> <文本> 私聊发送纯文本;all=当前待回复队列;成功后关待回复会话
bye <qq|all> 发送结束语并关闭会话(all=队列全部)
close <qq|all> 关闭会话、不发结束语(all=队列全部)
more 终端不支持;请在通知群使用 .more(引用消息、.more <QQ>;无参且队列仅 1 人可自动匹配;无 more all)
quit / exit / stop 优雅停止 bot(保存状态并退出)

终端说明:

  • 命令 ACK 只写本地,不向 QQ 群发送命令回执;unmute 的汇总提醒、以及群内 .more <QQ> 的合并转发属于业务消息,不在「命令 ACK」范围内。
  • 终端没有 more;历史合并转发只通过群内 .more / .more <QQ> 完成。
  • all 仅指当前待回复队列,不是全部好友;群内 say/bye/close 支持 all,.more 不支持 all;无参且队列仅 1 人时自动匹配。
  • say / bye 在客户端未运行时会本地拒绝;close 不要求客户端在线。

停止 bot 也可使用终端 Ctrl+C(或向进程发送 SIGTERM)。触发后会执行优雅关停:

  1. 停止接收新事件,取消后台任务(巡检 / 控制台 / 通知 ack 冲刷)
  2. 关闭 NapCat WebSocket 连接
  3. 最后一次将运行状态写入 state_file
  4. 进程退出

若信号处理器未能生效(极端情况),入口会兜底再保存一次状态。Shell 控制台的 quit / exit / stop 与 Ctrl+C 走同一优雅关停路径,不会只退出控制台而让 bot 继续跑。

配置说明

项目运行时读取根目录下的明文 config.yaml。

由于仓库是公开的,真实配置不得直接提交:

  • 仓库内提交:config.example.yaml(结构模板)、config.yaml.enc(SOPS + age 密文)、.sops.yaml
  • 本机私有:明文 config.yaml(已 gitignore)、age 私钥 *.agekey
  • 远端主机:解密后的明文 config.yaml,以及 /etc/notifybot/age.key(权限 600)

配置文件示例

# WebSocket 连接
ws_url: "ws://127.0.0.1:3001"
ws_token: "your_ws_token"

# 内部通知群号
internal_group_id: 123456789

# 白名单(这些 QQ 不会进入待回复队列)
whitelist:
  - 100000
  - 100001

# 超时里程碑(分钟)
milestones:
  - 15
  - 30
  - 60
  - 120
  - 180
  - 360
  - 720
  - 1440
  - 2880
  - 4320

# 最多监听多少条机器人发出的可操作消息
monitored_forward_limit: 15

# 会话结束语
closing_message: |-
  本次会话暂时结束。感谢您的支持与信任,再见。(请勿回复)

# 新好友通过后发送的欢迎消息
welcome_message: |-
  [自动回复]您好,感谢您添加好友。
  如有问题可直接留言,我们会在工作时间尽快回复。

# 群内命令防抖(秒)
debounce_seconds:
  say: 5
  more: 5
  bye: 5
  close: 5
  list: 5

# 好友申请去重窗口(秒)
processed_friend_requests_expire: 5

# 通过好友后,延迟多久再发送欢迎消息
friend_welcome_delay: 5

# 欢迎消息失败重试
friend_welcome_retries: 3
friend_welcome_retry_interval: 3

# 好友数阈值,达到后在群通知中附带警告
friend_count_limit: 3000

# 回复耗时统计最多保留多少条样本
reply_duration_maxlen: 1000

# 值班时间表:QQ -> 星期 -> 时间段列表
availability:
  123456789:
    monday:
      - ["09:00", "12:00"]
      - ["13:00", "18:00"]
    tuesday: []
    wednesday: []
    thursday: []
    friday: []
    saturday: []
    sunday: []

# 机器人消息监听有效期(秒)
max_listen_age: 86400

# 夜间免打扰配置
night_mode:
  start: "23:30"
  end: "07:00"
  summary_time: "07:00"

# 状态持久化文件
state_file: "archives/state.json"

# 会话归档目录(SQLite 会话库 archive.db 也存放在此)
archive_dir: "archives"

# 会话库保留期(天),0 = 永久保留
archive_retention_days: 0

# 构造合并转发时,最多回看多久以内的历史消息(秒)
recent_message_max_age: 86400

# 表情映射:动作 -> QQ 表情 ID
emoji_mapping:
  close: 75
  more: 289
  bye: 124
  say: 123
  cancel: 96
  recall: 89

# .say 发送成功后允许通过表情撤回的时间窗口(秒)
recall_window_seconds: 60

# AI 回复建议(OpenAI 兼容接口:DeepSeek / 通义 / GLM / one-api 等均可)
# 注意:客户对话内容会发送给所配置的 LLM 服务;api_key 属敏感信息,走加密配置流程
ai_suggestion:
  enabled: false
  base_url: "https://api.deepseek.com/v1"
  api_key: "sk-xxxx"
  # 图片输入需视觉模型:DeepSeek 请用 deepseek-flash(deepseek-v4-pro 不支持 vision)
  model: "deepseek-flash"
  vision_enabled: true
  image_detail: "low"
  max_images_per_suggestion: 3
  # system 提示词可自定义(多行),留空使用内置默认
  system_prompt: |-
    你是一家维修客服的助手。请根据以下客服与客户的最近对话,以客服身份草拟下一条发给客户的回复。
    要求:只输出回复内容本身,不要任何解释、前缀或引号;语气友好专业、简洁;如果客户诉求还不明确,先回应已知信息并礼貌追问。
  temperature: 0.7          # 采样温度,越高越发散
  timeout_seconds: 12       # 单次生成超时,失败不影响提醒正常发送
  max_context_messages: 20  # 送入模型的最近对话条数
  max_suggestion_chars: 300 # 建议文本截断长度

结构化消息示例

closing_message 和 welcome_message 除了支持字符串,也支持结构化消息段列表。当前代码内置支持的类型有:text、image、face、at、poke。

welcome_message:
  - type: text
    data:
      text: "您好,感谢您添加好友。"
  - type: image
    data:
      file: "https://example.com/welcome.png"

主要配置项

配置项 说明
ws_url / ws_token NapCat WebSocket 地址与令牌
internal_group_id 接收提醒、执行命令、查看状态的内部群号
whitelist 不参与待回复监控的 QQ 号
milestones 超时催办节点,单位为分钟
monitored_forward_limit 同时保留多少条可引用的机器人消息映射
debounce_seconds 各群命令的防抖窗口
processed_friend_requests_expire 好友申请去重窗口;也用于刚通过好友时的消息忽略窗口
friend_welcome_delay 通过好友后延迟多久再发送欢迎消息
friend_welcome_retries / friend_welcome_retry_interval 欢迎消息失败重试策略
friend_count_limit 好友数量告警阈值
reply_duration_maxlen 回复耗时统计样本上限
availability 当前可用成员时间表;用于提醒时随机 @ 一位值班成员
max_listen_age 机器人消息保持可操作状态的最长时间
night_mode 夜间免打扰起止时间与汇总时间
archive_dir 会话归档目录(含 SQLite 会话库 archive.db)
archive_retention_days 会话库保留天数,0 表示永久保留
state_file 运行状态持久化文件
recent_message_max_age 构造提醒合并转发时回看的消息时间窗口
emoji_mapping 表情 ID 到快捷动作的映射
ai_suggestion AI 回复建议(OpenAI 兼容接口配置);enabled: false 或缺省时功能关闭;vision_enabled 控制是否把对话图片 URL 送入视觉模型(DeepSeek 用 deepseek-flash);支持 .reload 在线开关

运行要求

  • Python 3.14+
  • 可用的 NapCat / OneBot WebSocket 服务
  • 一个用于接收提醒和执行命令的内部 QQ 群

安装

推荐使用 uv:

uv sync

如果你习惯使用 pip,也可以在满足当前 Python 版本要求的前提下安装项目依赖:

pip install -e .

启动

uv run python main.py

或:

python main.py

启动后程序会:

  • 加载 config.yaml
  • 恢复 state_file 中的运行状态
  • 启动每 60 秒执行一次的巡检任务
  • 连接 NapCat WebSocket,并在断开后自动重连
  • 首次连通后向内部群发送“机器人已启动”通知
  • 初始化当前好友数缓存

提醒、收尾与统计机制

新客户提醒

  • 客户首次发消息后不会立刻提醒
  • 只有当“距离最后一条客户消息已满 1 分钟”时,才会推送到内部群
  • 如果客户在等待期间继续发消息,会重置计时并清空已上报的里程碑
  • 开启 ai_suggestion 后,提醒合并转发的第一层会附上根据该客户最近对话生成的 AI 建议回复,供客服参考后用 .say 发送;生成失败或超时会自动省略,不影响提醒本身
  • vision_enabled: true 时,消息中的图片会先由 bot 下载并转为 data:image/...;base64,...,再以 OpenAI 兼容 image_url 送入模型(优先客户侧,最多 max_images_per_suggestion 张);下载失败的图片会跳过,请求失败或无可用图时自动回退 [图片] 占位/纯文本重试
  • 日志会记录每次批量生成的 成功/总数(含 0/N)以及提醒合并转发第一层实际附带的建议条数,便于排查夜间汇总后无建议等问题

超时催办

机器人会根据 milestones 的配置,在客户持续未获回复时逐级催办。每个客户每个里程碑只提醒一次;如果客户又发来新消息,该轮会话的里程碑记录会重置。

会话结束

以下行为会让客户从待回复队列中移除:

  • 客服直接私聊回复客户
  • 在内部群执行 .say
  • 在内部群执行 .bye
  • 在内部群执行 .close
  • 在内部群点击配置好的 bye / close 表情快捷操作
  • 客户在私聊窗口戳一戳机器人,且该客户当前仍在待回复队列中

状态面板

在内部群戳一戳机器人,或发送 .status,会返回状态面板,包括:

  • 运行时长
  • 当前待回复客户数
  • 已完结会话数
  • 最短、最长、平均、中位回复耗时
  • 当前监听中的机器人消息数量
  • 延后通知条数与临时静音状态

状态持久化与会话归档

运行状态(state.json)

state_file 保存待回复客户(含其会话周期 ID)、监听消息映射、命令防抖时间、夜间延后通知、临时静音截止时间和最近一次夜间汇总日期,重启后自动恢复。

会话归档(SQLite 双保险机制)

对话周期从客户进入待回复队列开始,到会话关闭结束。周期内发生的每一件事通过两层机制保证完整记录:

  1. 实时采集:周期内事件即时写入 archives/archive.db(SQLite,WAL 模式),崩溃/重启不丢、不依赖事后回拉
  2. 历史拉取对账:会话关闭后,按该周期的权威时间段 [started_at, ended_at] 拉取双方历史消息,与已入库内容按 message_id 对账,只补实时采集遗漏的部分(如客服在其他设备回复且上报异常),并写入一条对账审计事件

记录的事件类型:

事件类型 含义
session_open / session_close 周期开启/关闭(关闭含原因:say / bye / close / direct_reply / customer_poke 等)
customer_message 客户私聊消息(完整消息段)
staff_reply 客服回复(.say 发送或客服账号直接私聊,记录操作者/发送者)
bot_send 机器人主动发送给客户的系统消息(结束语、欢迎语等)
bot_notice 内部群提醒/催办(新客户提醒、里程碑催办、夜间汇总,含群消息 ID)
command 客服执行的会话操作命令(say / bye / close,含操作者 QQ)
poke 周期内的戳一戳
history_reconcile 关闭后的历史拉取对账结果(窗口、拉取/补录条数、缺失清单)

数据结构:sessions 表记录周期(客户、起止时间、耗时、关闭原因),session_events 表记录事件流(每条消息保留完整 OB11 消息段;图片等媒体保存 URL,注意 QQ 媒体链接会过期)。

检索示例:

-- 某客户的全部会话周期
SELECT * FROM sessions WHERE customer_uid = 123456 ORDER BY started_at;

-- 某个周期内的完整事件流
SELECT time, event_type, actor_uid, payload FROM session_events
WHERE session_id = 42 ORDER BY time;

-- 超过 1 小时才回复的会话
SELECT * FROM sessions WHERE duration > 3600 ORDER BY duration DESC;

回复耗时统计在重启后自动从会话库恢复最近样本,不再清零。

存量 JSONL 迁移

旧版本按 archives/<qq>_<yyyymmdd>.jsonl 追加写的归档可用导入工具迁入会话库(幂等,可重复执行):

uv run python scripts/import_jsonl.py --dry-run   # 预览
uv run python scripts/import_jsonl.py             # 导入

保留策略

archive_retention_days(默认 0 = 永久保留)设置保留天数后,巡检任务每小时清理超期的已完结会话。

AI 开发指引

如果你使用 Claude Code、Codex 或其他编码代理来修改这个项目,建议先阅读 napcat-sdk 的专用说明:

这样通常能更快对齐:

  • napcat-sdk 的核心对象和消息段类型
  • NapCatClient 的使用方式
  • 事件模型和 Reply / Text / NodeReference / NodeInline 等常见段类型
  • user_id / group_id 等字段的类型注意事项

已知限制

  • 多客户合并转发目前不能直接执行 .say / .more / .bye / .close,需要手动处理。
  • 可操作消息的监听依赖 monitored_forward_limit 和 max_listen_age;超出窗口后再引用旧消息,机器人可能返回“操作已过期”。
  • .say 的等待输入状态保存在内存中,程序重启后不会恢复。
  • 夜间汇总按客户聚合,不保留原始通知类型和里程碑层级。
  • 状态文件和会话库会随使用时间持续增长;会话库可通过 archive_retention_days 自动清理,状态文件需自行关注。
  • 归档中的图片等媒体仅保存 URL,QQ 媒体链接会过期,无法永久还原原始文件。

项目结构

.
├── main.py
├── config.yaml
├── pyproject.toml
├── uv.lock
├── README.md
├── scripts/
│   └── import_jsonl.py
├── archives/
└── src/
    ├── __init__.py
    ├── config.py
    ├── group_msg.py
    ├── history.py
    ├── main.py
    ├── message_sender.py
    ├── models.py
    ├── monitor.py
    ├── mute.py
    ├── new_user.py
    ├── private_msg.py
    ├── shell_console.py
    ├── state.py
    ├── storage.py
    └── utils.py

各模块职责:

  • src/main.py:程序入口逻辑、事件分发、启动通知、重连、优雅关停(信号→取消任务→关连接→落盘状态)
  • src/config.py:配置加载、全局状态初始化、NapCat 客户端实例
  • src/private_msg.py:处理客户私聊消息、客服私聊回复、私聊戳一戳
  • src/new_user.py:好友申请自动通过、欢迎消息发送、好友数提醒
  • src/group_msg.py:群命令、群表情快捷操作、群戳一戳状态面板
  • src/message_sender.py:提醒消息构造、合并转发、会话关闭、状态统计
  • src/monitor.py:每 60 秒巡检一次,负责新客户提醒、里程碑催办、夜间汇总、静音到期与过期清理
  • src/history.py:对话周期管理、实时事件采集、会话关闭后的历史拉取对账、启动恢复
  • src/storage.py:SQLite 会话库表结构与读写(sessions / session_events)
  • src/state.py:运行状态保存与恢复
  • src/mute.py:临时静音开关与延后通知汇总
  • src/shell_console.py:本地终端 Shell 控制台
  • src/utils.py:时长格式化、值班成员判断、夜间时段判断
  • src/models.py:TypedDict 类型定义
  • scripts/import_jsonl.py:存量 JSONL 归档导入工具

配置加密与远端热更新

本地改配置(推荐)

  1. 复制示例并编辑明文:
    cp config.example.yaml config.yaml
    # 编辑 config.yaml
  2. 确保本机已有可用的 sops 与 age,且 .sops.yaml 中的 age 公钥可用。 sops 推荐免污染安装,装入项目 .tools/bin/:Windows PowerShell powershell -ExecutionPolicy Bypass -File scripts\install-sops.ps1(无需 Git Bash);Git Bash / Linux / macOS 用 bash scripts/install-sops。也可系统级安装(Windows winget install Mozilla.SOPS,macOS brew install sops)。 加密使用 整文件 binary 模式(因为 availability 使用数字 QQ 号作为键,SOPS 结构化 YAML 模式不支持非字符串键)。
  3. 加密并提交密文:
    # Linux/macOS
    ./scripts/config-encrypt
    # Windows PowerShell
    ./scripts/config-encrypt.ps1
    
    git add config.yaml.enc .sops.yaml
    git commit -m "chore: update encrypted config"
    git push origin main
  4. push 到 main 后,GitHub Actions 会识别配置-only 变更:远端 git pull → 解密 → 原子替换明文 config.yaml → 不重启,等待 bot 热重载。

本地开发可把 age 私钥放在被忽略的 *.agekey(例如 .tools/notifybot.agekey),远端生产私钥固定为 /etc/notifybot/age.key。切勿提交私钥。

远端私钥

在部署主机放置 age 私钥:

sudo mkdir -p /etc/notifybot
sudo install -m 600 /path/to/age.key /etc/notifybot/age.key

也可用环境变量 SOPS_AGE_KEY_FILE 覆盖默认路径。

远端 sops 安装

部署主机的 sops 由 CI 在每次部署时通过 scripts/install-sops 自动安装到项目目录 .tools/bin/:不写系统路径、不需要 sudo、不污染主机环境,删掉项目目录即完全移除。版本固定在 workflow 的 SOPS_VERSION(当前 v3.13.3),下载后对照官方 checksums.txt 做 sha256 校验,幂等(已装同版本则跳过)。apply-encrypted-config / config-encrypt(bash 与 PowerShell 版)都会自动优先使用该副本,也可用 SOPS_BIN 显式指定。

若主机访问 GitHub Release 受限,可用 SOPS_RELEASE_BASE 覆盖下载源(指向相同目录结构的镜像)。

群内命令

  • .reload:只重读当前明文 config.yaml 并回报成败,不 git pull、不展示配置内容。
  • 兼容旧输入 .reload cfg / .update cfg,行为与 .reload 相同。
  • 自动生效以 Actions 为准;群命令仅作文件已就位后的手动 reload。

代码发布 vs 配置热更

同一工作流 .github/workflows/deploy-prod.yml 按变更分流:

变更内容 远端动作
仅 config.yaml.enc / 配置脚本 pull → 解密应用 → 等待热重载(不重启)
src/、依赖、启动相关等 pull → 解密(如有)→ uv sync → systemctl restart notifybot
两者都有 走完整重启路径

需要的 GitHub Secrets

  • SSH_PRIVATE_KEY、REMOTE_USER、REMOTE_HOST
  • 可选 REMOTE_PORT(默认 22)
  • 建议配置 Cloudflare Access Service Token:CLOUDFLARE_ACCESS_CLIENT_ID、CLOUDFLARE_ACCESS_CLIENT_SECRET

公开仓库注意

  • clone 公开仓库只能看到密文与 example,没有 age 私钥无法还原生产配置。
  • 历史中若曾提交过明文 token,请另行轮换;本流程负责后续不再明文入库。

后续可改进方向

  • 支持多客户合并转发下的拆分处理
  • 会话库的导出工具与群内检索命令(当前可用 SQL 直接查询 archives/archive.db)
  • 增加更细粒度的权限控制和群内角色区分

License

暂未声明。

About

适用于客服qq号的通知桥机器人,基于Napcat-SDK开发

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages