Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

中V歌词识别 · 歌曲库 (cv_lyric_context)

中文 VOCALOID(洛天依、言和、乐正绫等)歌词识别 + 歌曲库插件,两件事:

  1. 认歌词 —— 入站消息命中歌词时,向 MaiBot 的 LLM 上下文注入歌曲信息, 让 bot 能自然接住歌词话题
  2. 管歌库 —— 内置 VCPedia 爬虫,可以从 VCPedia 同步 4000+ 首中V歌曲(含作词作曲等完整创作信息)到本地 SQLite, 既能扩充歌词识别的词库,也能用 /歌词 命令或聊天直接查歌

词库有三个来源,可以叠加使用:

来源 数据 怎么来
内置基础库 3412 首旧快照,57227 句歌词 自带
VCPedia 同步 4000+ 首,含完整创作信息与歌词 /歌词 同步(后台爬取)
歌词文件 你自己放的歌 丢进 assets/lyrics_inbox//加歌

快速开始

# 1. 把插件目录放到 MaiBot 的 plugins/ 下,重启 MaiBot
# 2. 在 QQ 里发(先小批量试跑,确认数据正常再跑全量)
/歌词 同步 100
# 3. 同步完会自动重建识别词库,新歌立刻可被识别,不用重启
# 4. 试试
/歌词 搜索 普通DISCO
/歌词 歌曲 普通DISCO

Category:洛天依歌曲 递归后约 4000 个词条,按默认 0.8 秒间隔全量同步约 1 小时。

命令

命令 说明
/歌词 显示用法
/歌词 状态 本地歌曲数量、空歌词条数、上次同步时间、同步进度,附解析器自检结果
/歌词 同步 [数量] 后台增量同步,可限定本次抓取数量(如 /歌词 同步 100
/歌词 取消 中止正在进行的同步或批量补歌词
/歌词 重抓 <歌名> 重新抓取单个词条,刷新歌词 / 简介(常规同步不更新已入库的歌)
/歌词 补歌词 [数量] 批量重抓歌词为空的条目并回填(后台执行,非歌曲页自动跳过;不填数量用 sync_batch_limit

「补歌词」会给确认无歌词的条目(非歌曲页、无歌词章节)打上时间戳,在 crawler.refill_cooldown_days 天内跳过,避免它们堵在队首被反复重抓;抓取失败的不打,下次仍会重试。存量见 /歌词 状态 的「待补 / 近期已确认无歌词」。 | /歌词 搜索 <关键词> | 按歌名 / 歌手 / P主搜索 | | /歌词 歌曲 <歌名> | 查看歌曲详情与歌词 | | /加歌 | 导入 lyrics_inbox/ 里的歌词文件 |

聊天里直接问也行,LLM 会自动调用 search_vcpedia_song / get_vcpedia_lyrics

工作原理

用户消息 "某句歌词"
   │
   ├─ 监听入站消息 ──> 清洗文本(全半角/标点/大小写)
   │   新版: HookHandler chat.receive.after_process
   │   旧版: EventHandler ON_MESSAGE(回退)
   │                        在 57227 句关键词表中 O(1) 精确匹配
   │                        命中 -> 按会话登记 (时间戳, 歌词, 歌名)
   │
   └─ HookHandler (maisaka.planner.before_request + maisaka.replyer.before_model_request,
                   均 BLOCKING)
                            把 TTL 内的命中同时注入到规划器与回复器两条 LLM 链路:
                            · 规划器: 决定调用哪些工具前先看到歌词语境
                              (优先推荐/检索用户正在聊的歌),prompt/messages/
                              items 三种载荷结构都能追加
                            · 回复器: LLM 请求前,把命中整理成 system 内容注入
                              · 新版运行时传 items  -> 追加 SystemMessageItem 快照
                                (Context Item schema v1)
                              · 旧版运行时传 messages -> 追加 {"role": "system"}
                            两条链路各自幂等(含【歌词识别】标记就不重复叠加)

歌词文件 "某歌.txt"  ->  放进 assets/lyrics_inbox/
   │                     (/加歌 命令或插件加载时自动扫描)
   ├─ 解析: 去 LRC 时间轴与元数据标签行,歌名取文件名 / [ti:] / 首行
   ├─ 过滤: 含汉字少于 2 个的句子丢弃
   ├─ 补元数据: 文件写了用文件的,没写就反查基础库与 VCPedia 库
   ├─ 合并写入 assets/user_songs.json(同名歌合并歌词)
   ├─ 立即并入内存词库(不用重载插件)
   └─ 归档: 成功 -> imported/,失败 -> failed/

/歌词 同步  ->  VCPedia(Anubis PoW 反爬)
   ├─ 解 PoW 挑战换 auth cookie(缓存约一周,失效自动重解)
   ├─ MediaWiki api.php list=categorymembers 全量分页枚举,支持递归子分类
   ├─ 词条取 wikitext,解析出演唱/P主/作词/作曲/编曲/混音/调教/母带/PV/曲绘/年份/简介/歌词
   ├─ 写入 data/vcpedia_songs.db(增量,已入库的跳过)
   └─ 同步完自动重建识别词库(也可随时用 /歌词 搜索 查询)

注入的 system 内容形如:

【歌词识别】用户最近在会话中发送了以下歌词原文:
- 「某句歌词」 出自《歌名》(演唱:洛天依,P主:某P,年份:2020,作词:A,作曲:B,编曲:C,调教:D,混音:E,PV:F,曲绘:G)
  前后歌词:上一句 / 上一句 / 「被识别的这一句」 / 下一句 / 下一句
用户可能在引歌词、玩歌词接龙或聊这首歌。请在回复中自然地运用这些歌曲信息……
  • 命中行的前后歌词对接龙/续唱最有用——模型终于知道下一句是什么了
  • 创作信息(年份/词/曲/编/调教/混音/PV/曲绘)来自歌曲库,库里没有的歌自动跳过
  • 三类内容都有独立配置开关,见下方 plugin.inject_*;接龙用不到的信息可以关掉省 token

只在你主动要求时发言

插件平时只被动识别 + 注入上下文,不会自己发消息;bot 的回复仍由 MaiBot 主体生成, 只是"知道"了歌词背后的歌。唯一的例外是命令——发 /歌词 .../加歌 会收到一条回复。

数据

文件 说明
assets/knowledge_db.db 内置基础库,3412 首中V歌曲(歌名、P主、歌手)
assets/song_lyric_keywords.txt 歌词句 -> 歌名 关键词表,57227 句(匹配源)
assets/user_songs.json 歌词文件收件箱导入的歌
data/vcpedia_songs.db VCPedia 同步下来的歌(SQLite,songs + sync_meta 表)
data/anubis_cookies.txt 反爬 cookie,失效自动重解,可安全删除

song_lyric_keywords.txt 加载时会过滤含汉字少于 2 个的句子(纯数字/纯英文), 避免圆周率类歌曲的数字串误命中。

data/ 目录已在 .gitignore 中排除,不会入库。

添加新歌:丢歌词文件进收件箱

只需要一个歌词文件.txt.lrc 都行,放进 assets/lyrics_inbox/

assets/lyrics_inbox/
  ├── 普通朋友.txt          <- 放这里
  ├── 千本樱.lrc            <- LRC 也行
  ├── imported/             <- 导入成功后自动归档到这里
  └── failed/               <- 导入失败的文件放这里,不会反复重试

然后在 QQ 里发 /加歌/导入歌词/扫描歌词 同义),插件扫描收件箱、 把歌写进 assets/user_songs.json,并回复导入结果:

歌词导入完成:成功 2 个,失败 0 个。
- 《普通朋友》 入库 32 句,歌名取自文件名
- 《千本樱》 入库 48 句,歌名取自LRC标签
当前自定义歌单共 2 首。

plugin.auto_import_inbox 默认为 true,所以重载插件或重启 MaiBot 时也会自动 扫一遍收件箱,不一定要用命令。导入的歌立即生效,不用再重载。

/歌词/加歌 没反应怎么办

命令发完什么都没发生,按顺序查:

  1. 看日志里有没有 命令执行成功: lyrics_xxx —— 没有说明命令压根没触发, 跳到第 2、3 条;有说明触发了但消息没发出去,接着看 回复发送失败命令载荷里没有 stream_id
  2. [E_CAPABILITY_DENIED] … 未获授权能力: send.text —— manifest 的 capabilities 没写对。必须写点分的精确能力名 send.text, 写 send_message 这种粗粒度名字无效。改完要重启 MaiBot(manifest 在插件 加载前校验,热重载不生效)。
  3. 重启 MaiBot —— 新增的 @Command 组件要重新注册,热重载插件不一定生效。
  4. 到 WebUI 的「Bot 配置 → 命令」里看 lyrics_status 等在不在列表里 —— 1.2.0 起插件命令统一在这里管理(可配置放行用户/聊天流),没出现就是没注册上。

日志里出现命令执行成功但 回复发送失败 时,命令本身已经跑完(比如导入已完成, 会有 歌词文件已入库: …),只是结果没发出来;此时插件会退回让 bot 自己接一句话, 不会让你完全看不到反馈。

不依赖命令的退路:歌词文件放进 assets/lyrics_inbox/重载插件或重启 MaiBot, 加载时会自动导入,日志里会有 歌词文件已入库: …

歌名怎么来的

按顺序取,取到为止:

  1. 文件名(去掉扩展名和 (1) 这类副本后缀)——推荐,最省事
  2. LRC 的 [ti:歌名] 标签
  3. 文件第一个非空行——该行会被当作歌名,不计入歌词

文件名是 lyrics / 歌词 / 新建文本文档 这类通用名时,自动跳到第 2、3 条。

歌手和 P 主从哪来

按优先级,取到为止:

  1. LRC 标签 —— [ar:歌手] 填歌手,[by:] / [au:] / [re:] 填 P 主
  2. 文件名约定 —— 见下节,库里没有的歌用这个手工指定
  3. 从基础库反查 —— 歌名命中 knowledge_db.db(3412 首,歌手 3405 条、P主 3340 条) 就自动补上缺失的字段

《珍珠》的例子:歌词文件里什么都不写,因为库里有这首歌,导入后自动变成 演唱:洛天依,P主:洛天依官方账号

文件名约定(库里没有的歌)

改文件名就能指定歌手和 P 主,不用碰文件内容:

文件名 歌名 歌手 P主
珍珠.txt 珍珠
珍珠 - 洛天依.txt 珍珠 洛天依
珍珠 - 洛天依 - 某P.txt 珍珠 洛天依 某P
珍珠【某P】.txt 珍珠 某P
珍珠 - 洛天依【某P】.txt 珍珠 洛天依 某P
【洛天依】珍珠.txt 珍珠 洛天依
珍珠 - 洛天依、言和.txt 珍珠 洛天依、言和

规则:

  • 分隔符 -(半角减号两侧都要空格)或全角 (不要求空格)
  • 括号 【】 [] 标注 P 主;但括号在最开头时算歌手(中V 常见的 【歌姬】歌名 写法)
  • 半角减号两侧无空格时不拆,所以 X-02.txt光 -Hikari-.txt 这类歌名不会被拆坏
  • 如果完整文件名能在库里查到,就完全不拆分,直接用原名 + 库的元数据

规则细节:

  • 只补空缺,写了就不覆盖,库里有也不覆盖手填值
  • user_songs.json 里留空的字段不会冲掉库里的信息(早期版本会,已修)
  • 库里的歌手可能带换行(如 言和\n洛天依),注入时会压成「演唱:言和、洛天依」
  • 库里没有的歌就留空,注入时不加空括号

想改已经导入过的歌,直接编辑 assets/user_songs.jsonsingers / uploader 字段。

歌词文件怎么处理

  • 自动剥掉 LRC 时间轴([00:12.34],一行多个也能剥)
  • 跳过 [ti:] [ar:] [al:] [by:] [offset:] 等元数据标签行 ([ar:] / [by:] / [au:] / [re:] 的内容会取作歌手/P主,见上一节)
  • 空行去掉,文件内重复行只留一句
  • 含汉字少于 2 个的行(纯数字/纯英文)会被过滤,防止圆周率类歌曲误命中
  • 编码自动识别:UTF-8(含 BOM)/ GBK / Big5
  • 单个文件最多入库 plugin.max_lines_per_song 行(默认 2000)

同名歌不会重复添加,新歌词会合并进已有条目。

也可以直接编辑 assets/user_songs.json

手改、批量改时用这个:

[
  {
    "name": "歌名",
    "singers": "洛天依",
    "uploader": "某P",
    "lyrics": ["第一句歌词", "第二句歌词"]
  }
]

lyrics 也支持直接写字符串,用 \n 换行。

生效方式:插件在 on_load 时读取,所以在 MaiBot 里重载插件 (WebUI 关掉再打开)或重启 MaiBot 即可,无需重装。同名歌词句以自定义歌优先。

VCPedia 同步

同步下来的数据怎么用

两处,自动的:

  1. 扩充歌词识别词库 —— 插件启动时,以及每次同步完成后,都会读取 data/vcpedia_songs.db,日志里出现 外部歌曲库 vcpedia_songs.db: 新增 N 首歌 / 歌词库: 同步后重建索引,新增 N 首进入识别词库。 之后这些新歌的歌词句也能被识别命中,歌手/P主自动带上,不需要重启 MaiBot。 重建是幂等的:已有的歌跳过,只有新增的进索引(已有歌词的更新不会重索引)。
  2. 直接查歌 —— /歌词 搜索 /歌词 歌曲,或聊天里直接问(走 LLM 工具)。

规则:

  • 基础库已有的歌名不重复索引 —— 它的歌词已经在 song_lyric_keywords.txt 里了, VCPedia 只补新歌。所以日志里的"新增 N 首"通常小于库里的总条目数,这是对的。
  • VCPedia 的歌手/P主不覆盖基础库已有的值,只补空缺。
  • 没同步过、库是空的,都只是跳过,不影响歌词识别。
  • 实测 4000 首规模加载约 0.9 秒、内存约 31 MB,不会拖慢启动。

反爬与礼貌爬取

站点全站启用 Anubis PoW 反爬(明文请求含 api.php 一律 403), 插件内置解题逻辑与 cookie 缓存,无需手工配置。

实现上参考了 mohobotmusic_knowledge/vcpedia.py(MIT),并修正了它两处会失败的地方:

  • pass-challengeresponse 必须传真实 hash。mohobot 传固定值 1, 在 Anubis 1.27 会返回 invalid response.
  • redir 必须用重定向后的最终 URL。站点根路径会 301 到 /首页, challenge 是在 /首页 下发的,传初始 URL 会导致校验失败

请保持 crawler.request_interval 不要太小,别把站爬崩了。

换个分类,或接自己的歌库

crawler.categories 可以改,父分类会自动递归子分类:

[crawler]
categories = "Category:洛天依歌曲,Category:殿堂曲,Category:传说曲"

注意两件事:

  1. 改完配置必须重启 MaiBot(或重载插件)。WebUI 保存配置只写文件,不会推送给 插件运行器——改完直接 /歌词 同步 跑的还是旧分类。同步开始的消息里会回显 当前分类,跑之前对一眼。
  2. 换分类是增量不是替换。已有的歌不会删,新分类的歌追加进同一个 data/vcpedia_songs.db。想同时保留多个歌手就写成逗号分隔的列表,别来回换。

plugin.extra_song_dbs 可以额外接别的 SQLite(相对 MaiBot 根目录,多个用英文逗号分隔)。 库里只要有 songs(name, singers, uploader, lyrics) 四列就能用(多出的列忽略):

[plugin]
extra_song_dbs = "data/我的歌库.db"

留空则只加载内置爬虫同步下来的库。手填的路径必须落在 MaiBot 根目录内, 写成 ../../ 之类越界的会被拒绝并记日志。

配置(config.toml,Runner 自动生成)

默认 说明
plugin.enabled true 是否启用
plugin.min_line_len 4 参与匹配的歌词句最短字数(过滤过短误报),也用于歌词文件导入
plugin.ttl_seconds 600 命中后多久内注入有效(秒)
plugin.max_inject 3 单次注入最多携带的歌曲数(歌名去重)
plugin.inject_context_lines 2 注入命中歌词的前后各几行(0 表示只注入命中的那一句)
plugin.inject_basic_credits true 注入年份与作词/作曲/编曲
plugin.inject_full_staff true 注入调教/混音/PV/曲绘等完整 STAFF
plugin.auto_import_inbox true 插件加载时自动导入 lyrics_inbox/ 里的歌词文件
plugin.max_lines_per_song 2000 单个歌词文件最多入库的行数
plugin.extra_song_dbs 额外的歌曲库(SQLite)路径,留空只用内置爬虫同步下来的库
plugin.max_results 5 搜索歌曲时最多返回几条
plugin.detail_lyric_lines 30 /歌词 歌曲 展示的歌词行数(0 = 不展示)
plugin.lyric_preview_chars 120 工具返回歌词时的预览字数
crawler.base_url https://vcpedia.cn VCPedia 站点根地址
crawler.categories Category:洛天依歌曲 爬取分类,多个用英文逗号分隔
crawler.category_depth 2 子分类递归深度
crawler.request_interval 0.8 两次请求的最小间隔(秒),请保持礼貌爬取
crawler.timeout 20 单次请求超时(秒)
crawler.max_fail 30 连续失败达到该次数时提前中止同步
crawler.sync_batch_limit 0 单次同步最多抓取多少首,0 表示不限
crawler.allow_sync_command true 是否允许 /歌词 同步 触发同步
crawler.refill_cooldown_days 7 /歌词 补歌词 跳过多少天内已确认无歌词的条目,0 表示每次都重抓
crawler.verify_ssl true 是否校验 SSL 证书(见下)
crawler.ca_bundle CA 证书文件路径(PEM),用于有 TLS 中间人的网络
recommend.recommend_enabled true 是否启用 recommend_cv_song 推荐工具
recommend.target_singers 洛天依 目标歌手白名单(英文逗号分隔),推荐优先返回他们演唱的歌
recommend.known_virtual_singers 洛天依,言和,… 已知虚拟歌手名单;候选歌歌手列表里出现「名单中非目标歌手」即被过滤
recommend.allow_unknown_singer true 歌手字段为空的歌是否允许推荐(放行但标注「歌手未知」)
recommend.recommend_count 3 LLM 未指定数量时默认推荐几首
recommend.soft_exclude_size 10 最近推荐软排除窗口,窗口内不重复推荐(0 = 不去重)
emotion.annotate_on_sync true 同步完成后自动为待标注的歌打情绪标签(新歌优先)
emotion.annotate_on_sync_limit 30 每轮同步后最多标注多少首(防打爆 LLM 配额)
emotion.llm_task utils 标注用的模型任务名/模型标识(utils / planner / replyer / 具体模型名);留空走默认路由,若未配好会 fallback 向量模型报 400
emotion.annotate_timeout_ms 20000 标注单首歌的 LLM 超时(毫秒)
emotion.annotate_budget_seconds 180 每轮标注总时间预算(秒),超时即停下轮继续

氛围选歌(v2.5.0)

给曲库加了情绪标签层后,bot 可以根据聊天氛围主动推歌:

  • 标签固定 7 个:甜美、温柔、积极、帅气、搞怪、伤感、愤怒(一首歌可多标签)。
  • LLM 对话中判断用户想听歌/氛围合适时,会自己调用 recommend_cv_song 工具 (传入情绪标签),工具内完成:标签匹配 → 随机抽取 → 最近推荐去重 (默认 10 首窗口内不重复)→ 歌手归属校验(合唱、含其他虚拟歌手的歌 不进推荐池;歌手字段为空的放行但标注「歌手未知」)。
  • 歌词识别注入行为完全不受影响——校验只作用于推荐环节。

同步后自动标注(v2.6.0,默认开启)

/歌词 同步 的结果消息发出后,插件会自动给待标注的歌打情绪标签:

  • 新歌优先:按 id 倒序取队列,刚同步进来的歌先标,标完立刻能被 recommend_cv_song 推荐
  • 每轮最多 emotion.annotate_on_sync_limit(默认 30)首,总预算 180 秒,超时剩下的下轮继续
  • 任何失败都不影响同步:单首失败只记 warning;连续 3 次失败熔断本轮, 日志会直接给出三选一处置(配 model_config / 改 llm_task / 关开关)
  • 同步回执之外会单独补一条「(顺带完成 N/M 首情绪标注)」
  • 关闭:emotion.annotate_on_sync = false,热重载立即生效

llm_task 怎么填:默认 utils;若真机日志出现 plugin.org.mai-mai.cv-lyric-context ... embedding ... 400 Field required: input.contents, 说明该任务被路由到了向量模型——三选一:① model_config.toml 给任务 plugin.org.mai-mai.cv-lyric-context 配文本模型(根治);② emotion.llm_taskplanner / replyer 或具体模型名;③ 关掉 annotate_on_sync

与离线脚本的分工:同步自动标注走新歌优先,负责让新歌即插即用; 离线脚本 annotate_emotions.py从老到新,负责慢慢排空历史存量(约 4000 首)。 两条路径写同一张表,互不冲突:标过的自动出队,失败的留在队列里下轮重试。

批量标注情绪标签(真机执行,一次性)

标注用独立脚本 annotate_emotions.py不进 MaiBot 运行时,key 不落插件配置。

最省事的方式:双击 run_annotate.bat(不用开 PowerShell、不用敲命令)。 右键用记事本打开它,改这 4 行一次即可:

set "PYTHON=python"                                  :: python 不在 PATH 就填完整路径
set "API_KEY=PASTE_YOUR_API_KEY_HERE"                :: 你的 key
set "DB=E:\mai\maibot\data\plugins\org.mai-mai.cv-lyric-context\vcpedia_songs.db"
set "BASE_URL=https://ark.cn-beijing.volces.com/api/v3"
set "MODEL=REPLACE_WITH_YOUR_MODEL_ID"               :: 方舟 Model ID

双击运行后先干跑 3 首(不写库),问你是否全量跑,回 y 就开始。 想提速就给 set "EXTRA=--concurrency 4"

曲库在 MaiBot 分配给插件的持久化目录(默认 data/plugins/<plugin_id>/vcpedia_songs.db, 相对 MaiBot 根目录),不在插件源码目录的 data/——脚本必须用 --db 显式指向它 (或写进 annotate_config.jsondb 字段,就不用每次带参数了):

# 0. 找到真机运行时曲库(PowerShell,MaiBot 根目录下)
Get-ChildItem E:\mai\maibot\data -Recurse -Filter vcpedia_songs.db |
  Select-Object FullName, Length
# 认准大的那个(几 MB 级 = 有歌的;28KB = 空壳)

cd E:\mai\maibot\plugins\cv_lyric_context
$DB = "E:\mai\maibot\data\plugins\cv_lyric_context\vcpedia_songs.db"  # 按上面结果改

# 1. 配置 key(环境变量)
$env:MAIBOT_ANNOTATE_API_KEY = "sk-xxx"        # 或 OPENAI_API_KEY
# 端点/模型默认 OpenAI 官方 + deepseek-chat;自建/中转写 annotate_config.json:
#   {"base_url": "https://api.xxx.com/v1", "model": "deepseek-chat"}
# (该文件已 gitignore,不入库)

# 2. 先试 3 首看质量(--dry-run 只打印不写库)
python annotate_emotions.py --db $DB --limit 3 --dry-run

# 3. 全量跑:约 4000 首(有歌词的),1.5~2 小时
#    断点续跑:失败/中断的歌不写标签,下轮自动重试;Ctrl+C 随时安全退出
python annotate_emotions.py --db $DB

# 查看覆盖情况(标签分布统计)
python -c "import sys; from vcpedia_store import SongStore; print(SongStore(sys.argv[1]).emotion_stats())" $DB

# 某首标得不准?清掉重标:
#   sqlite3 $DB "UPDATE songs SET emotion='' WHERE name='歌名'"

标注依据是整首歌的歌词(超长歌词保留开头/结尾各 1500 字), prompt 要求「不要因为单句歌词而改变整体判断」,temperature 0.2。

无歌词的条目不参与标注,也不会被推荐。建议跑标注时停掉 MaiBot,避免 SQLite 写锁偶发冲突 (就算冲突也不丢数据:失败的歌不写列,重跑自动补)。

公司网络 / 安全软件导致证书错误

同步报 CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain, 说明你的网络出口做了 TLS 中间人(代理或安全软件把站点证书换成了自签的)。两种解法:

第一步:搞清楚是哪张证书

跑 MaiBot 的那台机器上执行(用 MaiBot 同一个 Python):

python find_root_ca.py                    # 诊断 vcpedia.cn
python find_root_ca.py --host baidu.com   # 换别的站确认是不是全局现象

它会连一次站点(不校验证书)并打印:

目标站点 : vcpedia.cn:443
证书主体 : commonName=vcpedia.cn
证书签发者: commonName=TestCorp Proxy Root CA, organizationName=TestCorp

>>> 这张证书由另一张 CA 签发,说明链路里多了一层。
>>> 要找的是它的【签发者】那张证书。

那个签发者就是要找的证书名,脚本会把后面的导出步骤一并打印出来。

第二步:导出并填进配置

  1. Win + Rcertmgr.msc 回车
  2. 展开「受信任的根证书颁发机构」→「证书」 (找不到就去「中间证书颁发机构」里再找一遍)
  3. 按「颁发给」排序,搜第一步拿到的签发者名字
  4. 右键 → 所有任务 → 导出 → 选 Base64 编码 X.509 (.CER)
  5. 存好后在 config.toml 里填:
[crawler]
ca_bundle = "C:/mai/proxy-root-ca.cer"

.cer.pem 内容一样(都是 Base64 PEM),改不改后缀都行。

用浏览器看也行:在那台机器上打开 https://vcpedia.cn → 地址栏锁图标 → 「连接是安全的」→ 证书图标 →「详细信息」/「证书路径」,最顶层那张就是根, 可以「导出」或「复制到文件」。

如果代理是本机软件,直接去它那儿拿更快:

软件 位置
Fiddler Tools → Options → HTTPS → Actions → Export Root Certificate to Desktop
Charles Help → SSL Proxying → Save Charles Root Certificate
mitmproxy ~/.mitmproxy/mitmproxy-ca-cert.pem
Clash Verge 设置里一般有「系统代理 CA」;找不到就用 find_root_ca.py 反查

临时方案:关掉校验

[crawler]
verify_ssl = false

这这会跳过证书链校验,存在被中间人窃听的风险。只在确认那个代理是你自己的 (公司网关、本机安全软件)时才用,公网上不要开。插件关闭校验时会打一条 warning 日志。

ca_bundle 指向的文件不存在或格式非法时,会自动回退到系统证书并记日志,不会让插件起不来。

排障日志

首次触发时各打印一行字段名诊断,日常运行打印命中与注入结果:

日志 含义
中V歌词识别已加载: N 句 / M 首 插件已加载、词库就绪
[诊断] chat.receive.after_process 字段: [...] 入站 hook 的实际字段名
歌词命中: 「…」-> 《…》 (会话=…) 识别成功
[诊断] import_lyrics 字段: [...] /加歌 命令首次触发,列出载荷里的实际字段名
收到歌词导入命令: raw=… stream_id=… 命令已触发;stream_id=<空> 说明取不到会话,回复发不出去
歌词导入结果已回复: sent=True/False 结果是否发出去;False 时插件会退回让 bot 自己接话
命令载荷里没有 stream_id,无法回复结果 命令触发了但回不了话,把字段列表反馈给开发者
[诊断] before_model_request 字段: [...] 回复器注入 hook 的实际字段名
[诊断] planner.before_request 字段: [...] 规划器注入 hook 的实际字段名
已向 LLM 上下文注入歌曲信息(items) 回复器注入成功
已向规划器注入歌曲信息(prompt/messages/items) 规划器注入成功
歌词命中但请求载荷中没有 items/messages 回复器侧既无 items 也无 messages,把字段名反馈给开发者
歌词命中但 planner 请求载荷中没有 prompt/messages/items 规划器侧无可用载荷,把字段名反馈给开发者
歌词收件箱导入: 成功 N 个 / 失败 M 个 本次加载扫过收件箱的结果
歌词文件已入库: xxx.txt -> 《歌名》(新增 N 句) 某个歌词文件导入成功
歌词收件箱导入失败: … 扫描/写盘异常,看完整堆栈
歌词库已加载: 本地 N 首歌曲,上次同步 … 内置 VCPedia 库就绪
歌词库: 解析器自检通过(新版:…) 进程里装的是新解析器,未闭合 <poem> 的页面能解析
歌词库: 解析器自检失败——旧版:… WARNING。磁盘文件可能是新的,但进程里的模块还是旧的,完整重启 MaiBot
外部歌曲库 vcpedia_songs.db: 新增 N 首歌 爬到的歌接进了识别词库
VCPedia: 同步失败: … 爬取异常,多为反爬拦截、限流或证书问题
hook 会话 … 无命中 类情况 会话 ID 与消息侧不一致(本插件两侧都取 session_id,一般不会出现)

同步失败怎么办

现象 原因与处理
CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain 网络出口有 TLS 中间人。配 crawler.ca_bundle 指到代理根证书,或临时 verify_ssl = false。详见上一节
pass-challenge 返回 403(invalid response.) 站点 Anubis 版本升级改变了校验方式,需对照前端 main.mjs 调整
同步全量失败、日志刷 403/超时 请求过密被临时限流。调大 crawler.request_interval,等一段时间再试
本地 getaddrinfo failed / 连不上站 网络环境的 DNS 拒绝解析该域名,换 DNS 或网络环境
未找到 Anubis challenge 脚本 站点可能已关掉反爬,或页面结构变了;看返回的状态码判断
库里没有某首歌 该曲不在配置的分类下;换 crawler.categories 或确认歌名
歌词为空 词条本身没有歌词章节(相声、纯音乐、器乐等非歌曲条目)
/歌词 重抓 回来「歌词 0 行」 先发「/歌词 状态」看解析器自检:不通过=进程里是旧模块,完整重启 MaiBot;通过=该页面结构特殊,用 check_lyrics_parse.py <歌名> 追踪解析步骤并贴输出
想抽查剩下那些空歌词条目 python list_empty_lyrics.py 30 列出名字(会标出哪些还没确认过),挑几首跑 python check_lyrics_parse.py <歌名> 看是真的没歌词还是解析漏了
命令发完没反应 见「/歌词 没反应怎么办」

本地测试

cd MaiBot插件开发
python check_plugin.py plugins/cv_lyric_context
python test_context.py           # 全流程模拟测试(189 项断言)

代码结构

文件 职责
plugin.py 入口:配置模型、生命周期、歌词识别与注入、/加歌
lyrics_import.py 歌词文件收件箱的解析与导入(纯标准库,可单独测)
vcpedia_mixin.py VCPedia 歌曲库能力:/歌词 系列命令 + 两个 LLM 工具
vcpedia_client.py Anubis PoW 解题 + MediaWiki API 取 wikitext
vcpedia_sync.py 同步流程:分类枚举、词条解析、入库、熔断
vcpedia_wikitext_parser.py wikitext -> 结构化创作信息
vcpedia_text_clean.py wiki 标记清洗({{color}}{{ruby}}<ref>[[链接|文本]]
vcpedia_store.py 歌曲库 SQLite 读写(含情绪标签列与方法)
singer_check.py 歌手归属校验纯函数(推荐池过滤用,可独立单测)
emotion_annotate.py 情绪标注纯函数件:prompt 构造 + 标签解析(离线脚本与插件内同步标注共用)
annotate_emotions.py 离线批量标注情绪标签脚本(真机跑,--db 指向运行时曲库,不走 MaiBot 运行时)
find_root_ca.py 诊断脚本:有 TLS 中间人时,查出该信任哪张根证书(不是插件的一部分,单独跑)

VCPediaMixin 以多继承混入主类(class CVLyricContextPlugin(VCPediaMixin, MaiBotPlugin)), 实测 MaiBot SDK 能正确注册继承来的 @Command / @Tool,不用把代码复制进 plugin.py

注意

  • 若你的 MaiBot 版本中 chat.receive.* 系列 hook 不存在(老版本),插件会退回到 ON_MESSAGE 事件;若该事件在版本里也未派发,则无法识别入站消息,需要换用对应 版本的消息 hook(可看日志里打印的字段名确认)。
  • 幂等保护:同一请求中若已注入过(内容含 【歌词识别】 标记),重试时不会重复叠加。 规划器与回复器两条链路各自独立判重,互不影响。
  • 注入覆盖两条链路:规划器(maisaka.planner.before_request,影响工具调用决策) 与回复器(maisaka.replyer.before_model_request,影响最终回复)。若某条链路 没有日志出现,说明该版本 MaiBot 未触发对应 hook 点。
  • 同一会话内相同文本 10 秒内重复到达只登记一次,避免两套监听重复计数。
  • 同步是增量且可中止的:已入库的歌会跳过,/歌词 取消 可随时停。

许可证

MIT。VCPedia 解析与清洗逻辑参考自 mohobot(MIT License)。

About

面向QQ机器人RAG的中文Vocaloid歌词上下文数据集,为MaiBot提供歌词与歌曲元数据。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages