nonebot-plugin-mantou-affection
为群聊机器人“馒头”设计的 NoneBot2 共享好感度系统。群友通过 /馒头互动 以及现有插件的真实互动积累好感;每日运势、戳一戳、水群榜和桃纸助手会读取同一份关系状态,逐渐改变文案、语气与展示。
功能
- 好感数据按“群号 + 用户”隔离
- 主动互动、冷却时间和每日次数限制(约 1/7 概率惹馒头不高兴,好感度 -1)
- 七级好感称号和群排行榜
- 自动感知其他插件的命令、正则及完整匹配 Matcher
- 每插件独立冷却、每日联动奖励上限,避免刷分
- 互动与联动共享每日好感获取总上限,最快约一年满级;额度用满后互动不会再扣好感
- SUPERUSER 手动增减群友好感度
- 群友发言时馒头按概率冒泡的小动作(好感度 >0 才触发,默认 0.05%),其中约 1/3 会升级为限时随机事件
- 群友发言时另有 0.05% 概率开一局「找字小游戏」:从题库抽一道二字型或三字型找字题,30 秒内抢答
- 戳一戳稳定 +1(受每日获取总上限约束),0.05% 概率戳出一次扣分翻倍的答题事件、0.05% 概率戳出一局找字小游戏
- 「馒头博弈」数字顺序竞猜:好感度 > 50 的群友发言时按概率开局(也可由其它插件调
maybe_start_bet), 三人各猜一个 1~5 的顺序,输家的好感度汇成奖池,平分给赢家和更接近的围观群友 - 「馒头桃子问答 PK」:榜上群友用
/馒头问答PK挑一位最近说过话的榜上群友,两边各答三道三选一, 输家队长拿出好感度的 10% 当奖池,分给获胜队长和答得不比队长差的队友 - 为其他插件提供好感上报与状态读取 API
- 使用
nonebot-plugin-localstore和原子写入持久化数据
安装
使用 nb-cli
nb plugin install nonebot-plugin-mantou-affection
使用 pip
pip install nonebot-plugin-mantou-affection
使用 Poetry
poetry add nonebot-plugin-mantou-affection
本地开发阶段也可以把 nonebot_plugin_mantou_affection 放进机器人项目的插件目录加载。
配置
全部配置均有默认值,零配置即可加载。在 NoneBot 项目的 .env 或对应环境文件中按需修改:
MANTOU_AFFECTION_BOT_NAME=馒头
MANTOU_AFFECTION_TIMEZONE=Asia/Shanghai
MANTOU_AFFECTION_TEXT_PATH=
MANTOU_AFFECTION_INTERACTION_LIMIT=5
MANTOU_AFFECTION_INTERACTION_COOLDOWN=7200
MANTOU_AFFECTION_MAX=999
MANTOU_AFFECTION_DAILY_GAIN_LIMIT=3
MANTOU_AFFECTION_RANKING_SIZE=10
MANTOU_AFFECTION_LINK_ENABLED=true
MANTOU_AFFECTION_LINK_NOTIFY=true
MANTOU_AFFECTION_LINK_DAILY_LIMIT=10
MANTOU_AFFECTION_LINK_COOLDOWN=300
MANTOU_AFFECTION_AMBIENT_ENABLED=true
MANTOU_AFFECTION_AMBIENT_PROBABILITY=0.0005
MANTOU_AFFECTION_AMBIENT_EVENT_RATIO=0.333
MANTOU_AFFECTION_EVENT_TIMEOUT=20
MANTOU_AFFECTION_UPSET_EVENT_TIMEOUT=60
MANTOU_AFFECTION_EVENT_TIMEOUT_PENALTY=5
MANTOU_AFFECTION_POKE_EVENT_CHANCE=0.0005
MANTOU_AFFECTION_FIND_CHAR_CHANCE=0.0005
MANTOU_AFFECTION_POKE_FIND_CHAR_CHANCE=0.0005
MANTOU_AFFECTION_FIND_CHAR_TIMEOUT=30
MANTOU_AFFECTION_BET_CHANCE=0.0005
MANTOU_AFFECTION_BET_MIN_AFFECTION=50
MANTOU_AFFECTION_BET_RECENT_MINUTES=5
MANTOU_AFFECTION_BET_WINDOW=60
MANTOU_AFFECTION_PK_COOLDOWN=3600
MANTOU_AFFECTION_PK_RECENT_MESSAGES=10
MANTOU_AFFECTION_PK_LOW_STOCK_WARNING=100
MANTOU_AFFECTION_LINK_REWARDS 是“插件模块名 -> 单次变化值”的 JSON 对象,支持正数和负数。默认值如下:
{
"nonebot_plugin_taozi": 2,
"nonebot_plugin_taozi_music": 2,
"nonebot_plugin_xianmei": 1,
"nonebot_plugin_daily_attendance": 2,
"nonebot_plugin_crystelf": 1,
"nonebot_plugin_chat_learning": 1,
"nonebot_plugin_msg_rank_card": 1,
"nonebot_plugin_bili_dynamic": 1,
"nonebot_plugin_miao": 1
}
MANTOU_AFFECTION_DAILY_GAIN_LIMIT 是每天通过互动和联动最多能获得的好感度总和(默认 3 点,互动与联动共享
同一份额度)。好感上限是 999,按默认值最快约 333 天满级;如果想严格满一年以上,可以设为 2(约 500 天)。
SUPERUSER 手动增减不受这个上限限制,/馒头好感 会显示当天已获取的点数。
额度用完之后不会再让好感倒退:/馒头互动 抽到 -1 时按 0 处理(delta=0),正向奖励也截断到 0,这时互动
仍然算一次、只是不再加分,文案会补一行「今天的好感已经拿满啦,明天再来吧。」;抽到发呆档(原始奖励就是
0)则直接用互动池里配对的那句旁白,不套用阶段文案,避免暗示加了好感。
MANTOU_AFFECTION_AMBIENT_ENABLED 控制群友发言时馒头是否可能冒泡,MANTOU_AFFECTION_AMBIENT_PROBABILITY
是单次触发的概率(0~1,默认 0.05%)。SUPERUSER 也可以用 /馒头反应概率 在运行时查看或覆盖,覆盖值写进
数据文件,重启后依然生效。群友发言时,只要他对馒头的好感度大于 0,馒头就有这个概率 @他冒出一句小动作
旁白;旁白不引用发言内容,已被命令接管的发言也不会触发,不会影响正常对话。
MANTOU_AFFECTION_FIND_CHAR_CHANCE 是群消息路径的找字小游戏概率(默认 0.0005,即 0.05%),
MANTOU_AFFECTION_POKE_FIND_CHAR_CHANCE 是戳一戳路径的找字概率(默认 0.0005),
MANTOU_AFFECTION_FIND_CHAR_TIMEOUT 是找字的作答时限(秒,默认 30)。找字只看自己的概率:好感度大于 0
的群友发言时先掷找字,没命中的那条消息才继续走 0.05% 小动作流程,所以两者互斥,不会在同一条消息上同时
出现。找字的两个概率可以用 /馒头找字概率 在运行时覆盖并持久化,和 MANTOU_AFFECTION_AMBIENT_ENABLED、
MANTOU_AFFECTION_AMBIENT_PROBABILITY 无关;把概率设成 0 就是关掉找字。
MANTOU_AFFECTION_BET_CHANCE 是「馒头博弈」的开局概率(默认 0.0005,即 0.05%),
MANTOU_AFFECTION_BET_MIN_AFFECTION 是群消息触发博弈所需的最低好感度(默认 50,只约束触发者、要严格大于;
对手不看好感度),
MANTOU_AFFECTION_BET_RECENT_MINUTES 是「最近说过话」的统计窗口(分钟,默认 5),
MANTOU_AFFECTION_BET_WINDOW 是博弈的作答窗口(秒,默认 60)。好感度超过门槛的群友发言时按
MANTOU_AFFECTION_BET_CHANCE 掷一次,命中就开局(戳一戳不参与);其它插件也可以调用 maybe_start_bet
主动开局,SUPERUSER 可以用 /馒头博弈概率 在运行时覆盖概率并持久化。
MANTOU_AFFECTION_PK_COOLDOWN 是「馒头桃子问答 PK」的冷却时间(秒,默认 3600,也就是一小时;0 即关闭冷却),
MANTOU_AFFECTION_PK_RECENT_MESSAGES 是挑对手时「最近说过话」的统计窗口(群消息条数,默认 10),
MANTOU_AFFECTION_PK_LOW_STOCK_WARNING 是问答题库余量提醒的阈值(默认 100)。前两项可以用
/馒头问答冷却 和 /馒头问答活跃 在运行时覆盖并持久化。
MANTOU_AFFECTION_AMBIENT_EVENT_RATIO 是小动作命中后升级为随机事件的比例(默认 0.333,也就是约 1/3)。
MANTOU_AFFECTION_EVENT_TIMEOUT 是普通答题时限(秒,默认 20),MANTOU_AFFECTION_UPSET_EVENT_TIMEOUT
是闹别扭答题时限(秒,默认 60,闹别扭题要慢慢哄),MANTOU_AFFECTION_EVENT_TIMEOUT_PENALTY
是普通题超时扣除的好感度点数(默认 5);闹别扭题超时按好感比例扣,不受这个配置影响。
MANTOU_AFFECTION_POKE_EVENT_CHANCE 是戳一戳戳出随机事件的概率(默认 0.0005,即 0.05%)。戳出来的事件是
「扣分翻倍」版:普通题的负向选项与超时按 2 倍结算,闹别扭题按好感比例算出的扣分(四个扣分档与超时,
各有保底)也再翻一倍,正向奖励(普通题 +1 / +2,闹别扭题 +2 / +5)不变。
随机事件
小动作触发时有约 1/3 概率变成一次限时随机事件,馒头会在群里抛出一个三选一的场景,并 @触发它的群友:
@群友 ⚡ 触发随机事件!
馒头的小本子从桌上滑下去,页角折了一道印子,它蹲在地上看了很久。
1. 把本子捡起来递回去,说下次放稳一点
2. 先蹲下来把折角一页页抚平,再问它今天记了些什么
3. 说一本本子而已,回头给你买个更贵的
大家都可以回答,直接发送 1、2、3 即可(答题不用@);
请在 20 秒内作答,被点名的群友超时未答好感度 -5!
群里任何人都可以作答(消息里也会写明),直接发 1、2 或 3 即可(不用 @机器人),每人只算第一次;
发别的内容不算作答,会一直等到窗口结束。同一个群同时只有一个活动——随机事件、找字小游戏、馒头博弈和
问答 PK 共用这个席位,上一次还没结束就不会再触发新的(这时新的小动作会回退成普通旁白)。
三个选项的顺序每次都会重新打乱,看起来都很合理,但只有最懂馒头心思的那个是最好的:最优 +2、普通 +1、 最差 -2,各自的加减记在答题者自己的好感度上。事件有两种来源,扣分不同:小动作升级来的按原值结算 (最差 -2、超时 -5),戳一戳戳出来的按2 倍结算(最差 -4、超时 -10),正向 +1 / +2 不受影响。窗口 结束时发一条合并消息,按答题先后每人一行;触发者必须作答,否则扣超时(点数可在配置里调整):
@甲 馒头开心地收下了「先蹲下来把折角一页页抚平,再问它今天记了些什么」,好感度 +2,当前 12
@乙 馒头嫌弃地躲开了「说一本本子而已,回头给你买个更贵的」,好感度 -2,当前 3
@触发者 馒头等不到你的回答,失望地走开了,好感度 -5,当前 5
闹别扭事件
好感度越高,这种题越容易冒出来:小动作升级成事件时,会按当前好感阶段抽一次题——neutral(Lv1-2)0%、
warm(Lv3)10%、close(Lv4)20%、flirty(Lv5)35%、intimate(Lv6-7)50% 概率换成闹别扭题库
(resources/affection_events_upset.json),其余情况仍是上面的普通题。戳一戳戳出的事件同样按这个比例抽题,
所以和馒头关系越好,戳出来的越可能是闹别扭题。
闹别扭题是六个选项(普通题仍是三个),消息里逐条列出来:
@群友 💢 馒头闹别扭了!
馒头把蒸笼盖扣上,自己缩在最里面,只露出一点点边。
1. 坐在旁边翻手机,等它自己出来
2. 隔着笼屉轻轻说一句:我等你出来,多久都等
3. 隔着笼屉喊:别闹了快出来,大家都在看着你
4. 隔着盖子问它:要不要先吃点东西再出来
5. 轻轻敲了敲盖子说:你不出来我可就走了啊
6. 算了,不理它,等它自己消气
大家都可以回答,直接发送 1、2、3、4、5、6 即可(答题不用@);
请在 60 秒内作答,被点名的群友超时未答好感度按比例大扣!
六个选项里有两档哄法(+5 最好、+2 次一级,都是固定加分、不按比例换算),另外四档全是扣分 (-3 / -5 / -10 / -15,绝对值同时是百分比和保底点数,好感越高扣得越狠,好感低时保底就是那个数字本身):
| 选项 / 来源 | 数量 | 小动作升级 | 戳一戳(再 ×2) |
|---|---|---|---|
最佳哄法(+5) |
1 个 | +5,固定 | +5,固定 |
次佳哄法(+2) |
1 个 | +2,固定 | +2,固定 |
轻扣(-3) |
1 个 | -max(3, 好感 × 3%) |
再翻倍 |
中扣(-5) |
1 个 | -max(5, 好感 × 5%) |
再翻倍 |
重扣(-10) |
1 个 | -max(10, 好感 × 10%) |
再翻倍 |
最重扣(-15) |
1 个 | -max(15, 好感 × 15%) |
再翻倍 |
| 触发者超时(没答) | — | -max(10, 好感 × 10%) |
再翻倍 |
结算文案会按档位告诉玩家错得有多离谱:+5「一下子被哄好了,这就是它最想听的那句」、+2「勉强收下了
这个台阶,其实还有更让它心动的回答」、-3「差一点点就说到它心坎上了」、-5「没说到点子上」、
-10「跑偏得有点远」、-15「完全踩到雷了」,戳一戳戳出来的闹别扭题共用同一套文案。
每个人按自己的当前好感结算(超时用触发者的好感),比例扣分向下取整:
30 好感:-3 / -5 / -10 / -15(都在保底档,超时 -10)
200 好感:-6 / -10 / -20 / -30(超时 -20;戳一戳再翻倍 → 最重扣 -60)
999 好感:-29 / -49 / -99 / -149(超时 -99)
@甲 馒头一下子被哄好了,这就是它最想听的那句,好感度 +5,当前 45!
@乙 馒头勉强收下了这个台阶,其实还有更让它心动的回答,好感度 +2,当前 42
@丙 差一点点就说到它心坎上了,馒头别过脸去,好感度 -3,当前 37
@丁(50 好感)没说到点子上,馒头把脸埋进蒸笼里,好感度 -5,当前 45
@戊(200 好感)跑偏得有点远,馒头听完更委屈了,好感度 -20,当前 180…
@己(200 好感)完全踩到雷了,馒头的毛都炸起来了,好感度 -30,当前 170…
@触发者(没答)馒头等不到你的回答,心凉了半截,好感度 -10,当前 30
普通题完全不受影响(正解 +2 / 普通 +1 / 陷阱 -2,超时 -5,戳一戳路径再翻倍,事件消息里会写明"超时好感度 -5!");闹别扭题因为扣分是比例制、发消息时无法预知数值,提示写成了「被点名的群友超时未答好感度按比例大扣!」。
事件带来的好感变化走管理员调整同一条路径,不受每日获取总上限和联动冷却限制。另外,/馒头互动 现在也有
约 1/7 的概率惹馒头不高兴,好感度 -1(好感度为 0 时不会变成负数),这时回复会换成一套「小情绪」文案。
找字小游戏
好感度大于 0 的群友发言时,馒头会先按 MANTOU_AFFECTION_FIND_CHAR_CHANCE(默认 0.05%)掷一次找字小游戏;
戳一戳也有 MANTOU_AFFECTION_POKE_FIND_CHAR_CHANCE(默认 0.05%)的概率戳出一局。两种来源开局时都会 @触发者
(戳一戳路径和随机事件一样把事件消息交给调用方发送,不额外 @任何人),然后从题库抽一道题贴出方阵。
题目分两种题型,都从 nonebot_plugin_mantou_affection/resources/findchar_puzzles.json 里抽:
二字型(pair):整块方阵铺满同一个字,只有一个位置藏着与它形近的另一个字,让群友找出来:
@群友 🔍 找字小游戏!
馒头把爪子按在方阵边上,一脸认真。
候 候 候 候 候 候 候 候 候
候 候 候 候 候 候 候 候 候
...(7 行 × 9 列,整块铺满同一个字,只有一个位置藏着形近的另一个字)
方阵里全是「候」,但还藏着 1 个和它长得很像的字;
找出它——直接发送那个字,或者发送它的位置(例如「3行5列」「第3行第5列」,行从上到下、列从左到右,都从 1 开始数);
大家都可以回答(答题不用@),请在 30 秒内作答;
答对好感度 +2,答错好感度 -5,被点名的群友超时未答好感度 -2!
三字型(trio):方阵里混着三个特别形近的字,两个干扰字有好多份、第三个字只藏了 1 个,消息里会点名 是哪两个干扰字,但不会说答案长什么样:
@群友 🔍 找字小游戏!
馒头说两个捣乱的字到处都是,别被带跑。
已 已 己 已 已 已 己 已
已 己 己 己 已 己 己 己
...(8 行 × 8 列,「己」「已」混排,只有一个位置是那个不一样的字)
方阵里混着大量的「己」和「已」,但还藏着另外一个和它们长得很像的字,全场只有 1 个;
找出它——直接发送那个字,或者发送它的位置(例如「3行5列」「第3行第5列」,行从上到下、列从左到右,都从 1 开始数);
大家都可以回答(答题不用@),请在 30 秒内作答;
答对好感度 +2,答错好感度 -5,被点名的群友超时未答好感度 -2!
- 开局消息不剧透:只点名干扰字(二字型点名铺满方阵的底字,三字型点名两个干扰字),答案字绝不会 出现在消息里;开场引导语也会按当前答案过滤,连机器人名字撞上答案字时都会整句省掉。答案和它的位置在 窗口结束的结算消息里才揭示(见下面的例子)。
- 群里任何人都可以作答(消息里也会写明,不用 @),每人只算第一次有效作答:
- 直接发送那个答案字(
strip后正好是一个汉字)就算一次作答,三字型里发成两个干扰字都算答错; - 发送位置也算,支持「3行5列」「第3行第5列」「5列3行」「第5列第3行」,有「行 / 列」关键字就按关键字 定顺序,数字可以是阿拉伯数字或中文数字(一~二十),行列都从 1 开始数;
- 其他内容(普通聊天、只写了「3行」这类缺一半的位置、非汉字的单个字符)不算作答,会被安静忽略。
- 直接发送那个答案字(
- 答对 +2、答错 -5,各自记在答题者自己的好感度上;窗口结束时按作答先后每人一行。触发者必须作答, 一次都没答过(答错也算答过)会扣 -2,提示会写明是超时未答:
@甲 馒头还没回过神,你已经找到了「巳」,好感度 +2,当前 14
@乙 猜错啦,那个字还躲在方阵里,好感度 -5,当前 5
@触发者 馒头把方阵收起来了也没等到你,好感度 -2,当前 18(超时未答)
答案是「巳」,在第 4 行第 6 列。
题库文件
题库是 nonebot_plugin_mantou_affection/resources/findchar_puzzles.json,一个题目数组(也兼容带 puzzles
数组的对象),每题长这样:
[
{"mode": "pair", "decoys": ["己"], "target": "已", "rows": 8, "cols": 10, "row": 3, "col": 7},
{"mode": "trio", "decoys": ["己", "已"], "target": "巳", "rows": 9, "cols": 12, "row": 5, "col": 2}
]
mode是pair或trio;decoys是干扰字(pair 1 个、trio 2 个);target是只出现 1 次的答案字;rows/cols是方阵尺寸(610 行 × 814 列);row/col是答案位置(从 1 开始,行从上到下、 列从左到右)。- 加载时会逐题校验(题型、干扰字个数、是否都是单个汉字、答案字不与干扰字重复、行列范围、答案位置是否 在方阵内),坏题跳过并记一条 warning,其余题目照常使用;题数不限,空题库或题库读取失败时找字小游戏 自动退回普通小动作 / 普通戳一戳。
- 二字型的所有非答案格都用那一个干扰字;三字型的两个干扰字按题号做确定性伪随机 50/50 混排,同一道题 每次渲染出来的方阵完全一样,答案字始终只出现 1 次。
- 题库随插件一起打包(
resources/findchar_puzzles.json),正式题库共 5000 题(二字型 2500 + 三字型 2500), 尺寸铺满 610 行 × 814 列、答案位置覆盖每一行每一列;往数组里追加题目不需要改代码,追加后重启机器人即可生效。
和随机事件一样,同一个群同时只有一个活动(小动作升级来的答题事件、找字小游戏、馒头博弈和问答 PK 共用这个
席位),上一个还没结束就不会再开新的,这时新的小动作会回退成普通旁白;窗口结束前的记录不会因为有人答对而
提前结束,
30 秒(MANTOU_AFFECTION_FIND_CHAR_TIMEOUT)到时统一结算。好感变化同样走管理员调整那条路径,不受每日
获取总上限和联动冷却限制。开场那句引导语按题型各有一套(各 24 条)随机抽取。
馒头博弈
「馒头博弈」是一局数字顺序竞猜:馒头摆出 1 2 3 4 5,一分钟后把顺序打乱并公布,谁猜得最接近谁赢,
输家要把好感度转给赢家。好感度严格大于 MANTOU_AFFECTION_BET_MIN_AFFECTION(默认 50)的群友发言时,
按 MANTOU_AFFECTION_BET_CHANCE(默认 0.05%)掷一次,命中就 @ 他和另外两位群友开局;戳一戳不参与。
插件自己维护一份「最近发言表」(每条群消息都会记,连被命令接管的发言也算),抽对手时用它。
其它插件也可以主动开局,走同一个入口:
from nonebot_plugin_mantou_affection import maybe_start_bet
started = await maybe_start_bet(
event.group_id,
event.user_id,
nickname=event.sender.card or event.sender.nickname,
candidates=[(member.user_id, member.name) for member in recent_speakers],
send=matcher.send,
)
candidates是最近几分钟在群里说过话的成员[(user_id, name), ...](触发者自己也在里面,插件会把它 排除掉),从剩下的人里随机抽两位当对手,凑不够两人直接返回False;对手不看好感度,0 或负好感的群友 照样能被抽中。也就是说:触发者要最近MANTOU_AFFECTION_BET_RECENT_MINUTES分钟内说过话且好感度严格大于MANTOU_AFFECTION_BET_MIN_AFFECTION(默认 50),另外两人只要是最近说过话的群友就行。send传调用方的matcher.send,用来发开场消息与结算消息,不传直接返回False。- 开局前会先占同群席位(和小动作答题、找字小游戏、问答 PK 共用,本群已有活动时返回
False);群消息触发时, 找字小游戏优先于博弈、博弈优先于小动作,三者不会在同一条消息上同时出现(问答 PK 只能由/馒头问答PK主动发起)。 - 窗口长度是
MANTOU_AFFECTION_BET_WINDOW(默认 60 秒),/馒头博弈概率可以在运行时改概率。
开局消息 @ 触发者和两位对手,只说明规则、不透露打乱后的顺序:
@群友 @甲 @乙 🎲 馒头博弈!
馒头把五张写着数字的纸片摊开,说今天玩点刺激的。
馒头摆出了五个数字:1 2 3 4 5;
60 秒后馒头会把这五个数字的顺序打乱,并公布打乱后的顺序;
被点名的三人必须在 60 秒内作答:直接发送你猜的顺序(五个数字,例如 3 5 1 4 2);
三个人的答案不能完全一样——如果三个人发的一模一样,三人各扣 5 好感度,游戏直接结束;
其它群友也可以回答(不用@),答案可以重复;
结算:被点名的人里最接近打乱后顺序的获胜(并列都算赢),输的人扣自己好感度的 5% 汇成奖池;奖池平分给赢家,以及比被点名的三个人都更接近的其它群友,其余群友不加不减。
作答:消息去掉空白和 、,,;;|/->→> 这类分隔符后,剩下必须正好是 1 2 3 4 5 的一个排列
(也接受中文数字「一二三四五」,全角数字同样可以);每人只算第一次有效提交,其他内容安静忽略。
结算:
- 相似度 = 打乱后的顺序与你的答案在对应位置上相同的个数(0~5)。
- 被点名的人里相似度最高的获胜;并列时所有达到最高分的人都是赢家(没有唯一赢家);没提交的被点名者算输家。
- 每个输家扣
max(1, 好感度 × 5%)(好感度为 0 时扣 0),这些点数汇成奖池。 - 分奖池的人 = 被点名者里的赢家(并列都算)+ 相似度严格大于被点名者最高分的围观群友;奖池在这些人 之间平分,除不尽的部分给其中最早提交答案的人(总额守恒)。
- 围观群友没超过被点名者最高分时不加不减(会有一条提醒);奖池为 0 时谁也分不到,消息里会照实说明。
- 三个被点名的人一个都没提交时不转让好感度、也不评判围观群友,只发一条馒头失落的收尾。
如果三个被点名的人都提交且答案完全一样,游戏当场结束:三人各扣 5 好感度,不再等窗口、不结算, 其它人也拿不到奖励。窗口结束时的结算消息会先揭晓打乱后的顺序,再逐条列出每个人的结果:
馒头转过身去,纸片在爪子里响了一阵。
馒头打乱后的顺序:3 1 5 2 4
馒头把纸片翻回来,歪着头看大家的表情。
@甲 猜中 5 个位置,赢得 5 点好感度,当前 105
@触发者 没提交答案,被扣掉 5 点好感度,当前 95
@乙 猜中 0 个位置,被扣掉 5 点好感度,当前 95
@丙 猜中 3 个位置,没有超过被点名的人,本次不加不减
并列获胜时两位赢家会互相 @ 出来,奖池平分(除不尽的余数归最早提交的那位):
@乙 猜中 5 个位置,与 @甲 并列获胜,分得 1 点好感度,当前 41
@甲 猜中 5 个位置,与 @乙 并列获胜,分得 1 点好感度,当前 41
@触发者 猜中 3 个位置,被扣掉 2 点好感度,当前 38
围观群友比被点名的人更接近时,会和赢家一起瓜分奖池(行里写明了是从奖池分的):
@甲 猜中 3 个位置,与 @乙 并列获胜,分得 3 点好感度,当前 103
@乙 猜中 3 个位置,与 @甲 并列获胜,分得 1 点好感度,当前 101
@触发者 猜中 0 个位置,被扣掉 5 点好感度,当前 95
@路人 猜中 5 个位置,比被点名的人更接近,从奖池分得 1 点好感度,当前 11
好感度的加减同样走管理员调整那条路径,不受每日获取总上限和联动冷却限制。
博弈题库
题库是 nonebot_plugin_mantou_affection/resources/bet_rounds.json,一个题目数组(也兼容带 rounds
数组的对象),每题长这样:
[
{"target": [3, 1, 5, 2, 4], "opening": "开场氛围句", "reveal": "揭晓氛围句", "settle": "结算氛围句"}
]
target必须是1~5的一个排列;opening/reveal/settle是三句氛围文案,分别用在开场消息、 揭晓顺序那一条消息和结算消息里,要求非空、无换行、不含阿拉伯数字(避免提前剧透顺序),长度 2~100 字。- 加载时逐题校验,坏题跳过并记一条 warning,其余照常使用;题数不限,空题库或读取失败时
maybe_start_bet直接返回False(不会报错)。 - 题库随插件一起打包(
resources/bet_rounds.json),正式题库共 5000 题:120 个排列各有约 40 题,三列氛围文案各 5000 句互不重复;往数组里追加题目不需要改代码,追加后重启机器人即可生效。
馒头桃子问答 PK
「馒头桃子问答 PK」是一局两边对答的三选一问答:发起者从好感榜上、最近说过话的群友里挑一位对手, 馒头把两边分成 A、B 两队(先后随机),每队各答三道题,答对多的一边获胜,输家队长拿出好感度当奖池。
玩法
-
发起:群里发送
/馒头问答PK(别名/馒头PK)。发起者必须已经在本群好感榜上(好感度大于 0, 且在榜单截断范围内,默认好感最高的 10 人),否则会提示先去/馒头互动攒好感; 本群已有其它活动(小动作答题、找字、博弈、另一局 PK)时开不了。 -
选人:馒头列出所有候选对手,
MANTOU_AFFECTION_PK_RECENT_MESSAGES(默认 10)条群消息内说过话、 且在好感榜上的其他群友才算候选(插件自己维护这份「最近发言表」,被命令接管的发言也算);不够一人时不开局。 发起者有 60 秒回复编号,超时或没选就取消这一局(不写冷却)。🍑 桃子问答 PK! 想和谁 PK?请在 60 秒内回复编号: 1. 路人甲 2. 路人乙 3. 路人丙 -
定先后:选人成功后随机决定哪边先答,并开始记冷却:
馒头把签筒摇了摇:先让 桃友 答题,后让 路人甲 答题。 -
六道题:先 A 队连答三道,再 B 队连答三道,每题窗口 60 秒。题目消息 @ 该队队长,规则是任何人都能答 (直接发
1、2、3,不用 @),但第一次作答就锁定阵营:在哪边出的题里先作答,就算哪边的人, 之后到对面队的题里作答会被提醒「你已经参与了 X 的队伍,不能再参与 Y 的队伍了」;同一边后面的题可以继续答。 每题只认第一个提交的人,其余同题的作答安静忽略。@桃友 第 1 题:桃子大量成熟的季节一般是? 1. 深冬 2. 夏天 3. 初春 60 秒内作答,直接发 1、2、3 即可(答题不用@)每题窗口结束后公布答案,并逐行列出这一题作答的人:
第 1 题答案:2. 夏天 @路人乙 答对了 @路人丙 答错了 -
结算:一边的总分是该边所有人答对数之和(队长答的也算进去)。答对数多的一边获胜;两边一样多算平局, 谁都不用拿出好感度。
🍑 桃子问答 PK! @桃友 A 方答对 1 题 @路人甲 B 方答对 0 题 @桃友 这一边赢下了这局 PK! @路人甲 输掉了 PK,拿出 4 点好感度当奖池,好感度 -4,当前 36 @桃友 获胜队长分得 2 点好感度,当前 62 @路人乙 答对 1 题,和队长一起分得 1 点好感度,当前 31 @路人丙 答对 0 题,和队长一起分得 1 点好感度,当前 21- 输家队长拿出
max(1, 当前好感度 × 10%)(好感度为 0 时拿 0)当奖池。 - 分奖池的人 = 获胜队长 + 获胜方里答对数不低于队长的其他人(队长一题没答对时,赢的那一边人人都能分)。
- 奖池在这些人之间平分,除不尽的部分给队长;平局只发一句平局文案,没有每人一行。
- 没分到的人也会各占一行(队友写着「没达到队长的手气」,对面写着「队伍输了」),好感度加减不受每日 获取总上限和联动冷却限制。
- 输家队长拿出
-
冷却:正式开赛后按
MANTOU_AFFECTION_PK_COOLDOWN(默认 3600 秒)给发起者记冷却,冷却期间再发/馒头问答PK会提示还要等多久;到点会在群里 @ 他提醒「又能发起问答 PK 啦」。冷却按「群 + 人」记, 写进数据文件,机器人重启后会给好感度榜成员与有冷却记录的人补发提醒(冷却已经到期的记录也照样补发), 之后清空全部冷却记录,相当于一次冷却刷新。
问答题库
题库是 nonebot_plugin_mantou_affection/resources/pk_questions.json,一个题目数组(也兼容带 questions
数组的对象),每题长这样:
{
"questions": [
{"text": "题干", "options": ["选项一", "选项二", "选项三"], "answer": 0}
]
}
text是题干,options必须正好三个非空选项,answer是正确选项下标(0、1、2)。- 加载时逐题校验,坏题跳过并记一条 warning,其余照常使用;读取失败或题库为空时
/馒头问答PK会提示 SUPERUSER 补题,不会报错。 - 每开一局从没出过的题里抽六道互不相同的题,抽到就立刻标记为已用并写进数据文件,重启也不会重复出题。
- 余量不足六题时开不了局(会说明还剩几题);余量低于
MANTOU_AFFECTION_PK_LOW_STOCK_WARNING(默认 100)时, 会在结算后私聊提醒所有 SUPERUSER 补题(同一次低余量只提醒一次,补题回升到阈值以上后重新计数)。 SUPERUSER 也可以用/馒头问答题库随时查看「还剩几题 / 共几题」。 - 题库随插件一起打包(
resources/pk_questions.json),正式题库共 10000 题:题干两两不重复、每题三个 互不相同的选项,正确选项落在 0、1、2 三个位置;往数组里追加题目不需要改代码,追加后重启机器人即可生效。
大型文案库
内置文案位于 nonebot_plugin_mantou_affection/resources/affection_texts.json,按“插件场景 + 好感阶段”组织。
mantou.interact、daily_attendance.fortune、crystelf.poke、msg_rank_card.rank、taozi.fortune 和
taozi.lexicon 各 5 个阶段 × 1000 条;mantou.ambient 是馒头在群友发言时按概率冒泡的
小动作旁白,每个阶段 2000 条;这些大场景合计 40000 条。
每个场景分别配置 neutral、warm、close、flirty、intimate 数组。
/馒头互动 的回复取自 mantou.interact 场景:好感度奖励仍按原有分布随机(-1~3 点),回复文案则随互动后的
好感阶段变化,从初见时的礼貌疏远逐渐变成亲密;文案中的 {bot} 会被替换为配置的机器人名字。抽到发呆档
(奖励 0)时会直接用互动池里配对的那句旁白,不走阶段文案。
msg_rank_card.rank 场景为水群榜卡片上每位群友名字旁的极短好感短评(4~16 字),随好感阶段变化。
mantou.ambient 场景只描写馒头自己的动作神态(4~30 字,不需要 {bot} 占位符),不回应群友说了什么,
语气同样随好感阶段从礼貌走向亲密。
mantou.interact.negative 是互动扣好感时专用的「小情绪」场景(10~40 字,可用 {bot}):neutral 疏远
尴尬、warm 小委屈、close 闹别扭、flirty 吃醋失落、intimate 受伤但包容。crystelf.poke.negative
是同结构的被戳烦了的短情绪(8~40 字,不含占位符),保留给以后的负面场景使用,当前代码不再引用它。
这两个场景目前每个阶段先放 4 条占位文案,之后会继续扩充。
随机事件库位于 nonebot_plugin_mantou_affection/resources/affection_events.json(普通题,3 选项)与
affection_events_upset.json(闹别扭题,6 选项,正式题库 5000 题,每题 6 个选项文案互不重复),
结构都是“场景 + 选项列表”:
{
"events": [
{
"text": "场景描述",
"options": [
{ "text": "最优选项", "delta": 2 },
{ "text": "普通选项", "delta": 1 },
{ "text": "最差选项", "delta": -2 }
]
}
]
}
普通题必须正好 3 个选项、delta 为 +2、+1、-2 各一个;闹别扭题必须正好 6 个选项,delta 必须是
+5、+2 各一个(两档哄法)、-3、-5、-10、-15 各一个(四个按比例扣分的档位)。旧结构的闹别扭题
会被校验拒绝并跳过。选项在发送前都会打乱顺序再编号,
答题时按编号对应的选项是否存在来判断(普通题发 4 会被忽略);结构不合规的条目会被跳过并记录警告。
事件库为空时随机事件自动退回普通小动作旁白。
如果想要覆盖内置文案或继续扩充,可以把同结构 JSON 放在 bot 的数据目录外,并设置:
MANTOU_AFFECTION_TEXT_PATH=/absolute/path/to/mantou_affection_texts.json
外部文件只需写想覆盖的场景或阶段,其他部分继续使用内置文案。插件会检查文件修改时间并自动重载,
不需要改 Python 代码;格式错误时会保留内置文案并记录警告。戳一戳默认随机抽取,运势、桃签等场景
可以传入日期等 seed,让同一天的文案保持稳定。
例如只保留桃纸助手和每日运势联动:
MANTOU_AFFECTION_LINK_REWARDS={"nonebot_plugin_taozi":2,"nonebot_plugin_daily_attendance":2}
使用方法
| 指令 | 权限 | 范围 | 说明 |
|---|---|---|---|
/馒头互动 |
群员 | 群聊 | 和馒头互动,好感 -1~+3 点(每日 5 次,间隔至少 2 小时;受每日获取总上限约束,额度满后只扣次数不扣好感) |
/馒头好感 |
群员 | 群聊 | 查看好感度、关系称号、今日获取量和联动额度 |
/馒头好感榜 |
群员 | 群聊 | 查看本群好感度排行榜 |
/馒头好感帮助 |
群员 | 群聊 | 查看菜单 |
/馒头好感调整 @群友 +10 |
SUPERUSER | 群聊 | 手动增加或扣除好感度 |
/馒头好感重置 确认 |
SUPERUSER | 群聊/私聊 | 清空所有群所有群友的好感度(需二次确认) |
/馒头反应概率 5% |
SUPERUSER | 群聊/私聊 | 查看或调整小动作触发概率(持久保存) |
/馒头找字概率 5% |
SUPERUSER | 群聊/私聊 | 查看或调整找字小游戏概率;只写一个值会同时设置群消息和戳一戳,分开设置写成 /馒头找字概率 群消息 5% 戳一戳 2%(持久保存) |
/馒头博弈概率 0.05% |
SUPERUSER | 群聊/私聊 | 查看或调整馒头博弈的开局概率(默认 0.05%,0 即关闭;持久保存) |
/馒头问答PK |
群员 | 群聊 | 和榜上群友来一局问答 PK(别名 /馒头PK;发起者要在好感榜上,且本群没有别的活动) |
/馒头问答冷却 30分钟 |
SUPERUSER | 群聊/私聊 | 查看或调整问答 PK 的冷却(默认 1 小时,支持 1800、30分钟、1小时 写法,0 即关闭;持久保存) |
/馒头问答活跃 20 |
SUPERUSER | 群聊/私聊 | 查看或调整挑对手时的活跃消息数(默认 10,范围 1~1000;持久保存) |
/馒头问答题库 |
SUPERUSER | 群聊/私聊 | 查看问答题库还剩多少题 |
插件联动
插件注册了 NoneBot run_postprocessor。其他插件的 Matcher 成功执行后,如果:
- 来源插件出现在奖励表中;
- 事件来自群聊;
- Matcher 是命令、正则、完整匹配、关键词、开头或结尾匹配之一;
- 本次执行没有抛出异常;
- 没有触发该插件的联动冷却或每日上限;
则按配置增减发起者的好感度,并在好感真正变化时补一条轻量提示(MANTOU_AFFECTION_LINK_NOTIFY,默认开启,
提示形如「馒头好感度 +2,当前 12」,不 @任何人;发送失败只记日志,不影响原插件)。普通 on_message 监听器不会自动变化,避免聊天学习、消息统计等插件在每条群消息上刷分。每日联动上限按变化值的绝对值计算;正向奖励还要与 /馒头互动 共享每日获取总上限,额度用完后只做截断,不再增加好感度。
主动上报 API
对于自动推送、戳一戳、献媚成功等无法从 Matcher 类型准确判断的事件,其他插件可以软依赖本插件并主动上报:
from nonebot import require
require("nonebot_plugin_mantou_affection")
from nonebot_plugin_mantou_affection import (
add_affection,
change_affection,
get_affection,
get_affection_response,
get_affection_snapshot,
maybe_start_bet,
poke,
)
actual = await add_affection(
group_id=event.group_id,
user_id=event.user_id,
delta=2,
nickname=event.sender.card or event.sender.nickname,
source="nonebot_plugin_xianmei:flatter",
)
# 也可以让某个明确行为降低好感度
actual = await change_affection(
group_id=event.group_id,
user_id=event.user_id,
delta=-1,
nickname=event.sender.card or event.sender.nickname,
source="another_plugin:bad_action",
)
# 当前数值,以及适合调整语气的 neutral/warm/close/flirty/intimate 分层
score = await get_affection(event.group_id, event.user_id)
snapshot = await get_affection_snapshot(event.group_id, event.user_id)
print(snapshot.affection, snapshot.level, snapshot.title, snapshot.band)
# 按场景抽取文案;seed 非空时同一输入稳定返回同一句
response = await get_affection_response(
"crystelf.poke",
event.group_id,
event.user_id,
)
print(response.text)
add_affection 与 change_affection 都会遵守每日联动上限、每日获取总上限和同一 source 的冷却,并返回实际变化值。add_affection 只接受正数,change_affection 接受正负数,其中负向变化不受每日获取总上限限制;两个读取 API 不会改变数据。
水群榜这类发完卡片想顺势开一局小游戏的插件,用 maybe_start_bet(见上面的「馒头博弈」章节):传榜单成员
和 matcher.send,命中概率时它会自己发开场消息并在窗口结束后发结算消息,返回 True。
戳一戳 API
戳一戳的判定收在本插件里,调用方一行接入即可:
from nonebot_plugin_mantou_affection import PokeResult, poke
result: PokeResult = await poke(
group_id=event.group_id,
user_id=event.user_id,
nickname=event.sender.card or event.sender.nickname,
send=matcher.send, # 可选:戳出随机事件或找字小游戏时用来发送事件消息与结算
)
await matcher.finish(result.text)
PokeResult 有两个字段:delta(本次实际好感变化)、text(回复文案,{bot} 已替换)。
机制:
- 每次戳稳定 +1,不会扣好感;这个 +1 受每日获取总上限约束,额度用完时
delta=0,文案会补一行 「今天的好感已经拿满啦,明天再来吧。」 text在delta != 0时末尾会追加一行「好感度 +1,当前 12」这样的提示;delta=0时只有文案本身。- 有
MANTOU_AFFECTION_POKE_EVENT_CHANCE(默认 0.05%)的概率戳出一次随机事件:这时text就是事件消息 本体(不带戳一戳文案,也不 @任何人),事件消息发到群里后任何人发1/2/3作答,窗口结束统一结算。 抽题和普通小动作一样看触发者的好感阶段,所以亲密关系下也可能戳出闹别扭题(消息前缀是 「💢 馒头闹别扭了!」)。结算的翻倍规则:普通题的负向选项 -4、超时 -10;闹别扭题的比例扣分算完后再翻倍 (好感 200 时最重扣 -30 → -60、超时 -20 → -40),正向奖励(普通题 +1 / +2,闹别扭题 +2 / +5)不变。 - 本群已经有未结束的事件、事件库为空,或调用时没有传
send,都会退化成普通戳一戳文案(+1 照常入账)。 - 随机事件没触发时,还会再按
MANTOU_AFFECTION_POKE_FIND_CHAR_CHANCE(默认 0.05%)掷一次找字小游戏:命中时text是找字方阵本体(同样不 @任何人,结算走send),方阵和作答规则见上面的「找字小游戏」。 - 文案统一做
{bot}→ 配置机器人名替换;文案库缺失或场景为空时回退为「馒头朝你笑了笑。」。
⚡ 触发随机事件!
馒头的小本子从桌上滑下去,页角折了一道印子,它蹲在地上看了很久。
1. 把本子捡起来递回去,说下次放稳一点
2. 先蹲下来把折角一页页抚平,再问它今天记了些什么
3. 说一本本子而已,回头给你买个更贵的
大家都可以回答,直接发送 1、2、3 即可(答题不用@);
请在 20 秒内作答,被点名的群友超时未答好感度 -10!
当前工作区还完成了四类展示联动:每日运势随关系阶段改写短句;Crystelf 戳一戳会增加好感并改变回复;水群榜展示每位群友的馒头好感;桃纸助手的桃签和词典卡片会出现不同程度的“馒头私语”。所有接入均为软依赖,好感插件未加载时原插件照常运行。
当前工作区中的 nonebot-plugin-xianmei 已在真正发出献媚文案时上报 +2,nonebot-plugin-chat-learning 已在真正回复成功时上报 +1;两处均为软联动,好感度插件未加载时不会影响原插件运行。
称号
| 好感度 | 称号 |
|---|---|
| 0 | 初次见面 |
| 10 | 有点眼熟 |
| 30 | 愿意靠近 |
| 60 | 悄悄在意 |
| 100 | 暧昧升温 |
| 160 | 特别偏爱 |
| 250 | 心照不宣 |
数据存储
数据由 nonebot-plugin-localstore 保存为插件数据目录中的 affection.json。文件采用临时文件写入后原子替换,插件不会在 Python 安装包目录内写运行数据。
除了好感数据,这个文件还保存 SUPERUSER 在运行时改过的概率/时长(settings)、问答 PK 的冷却记录
(cooldowns,按群号和用户号记到期时间戳)和已经出过的题目下标(pk_used_questions);缺少这些字段的
旧数据文件可以照常读取,插件会补上默认值。
开发与测试
poetry install
poetry run python -m compileall nonebot_plugin_mantou_affection
poetry run pytest
poetry run ruff check .
许可证
MIT
Metadata
Release files for nonebot-plugin-mantou-affection 0.15.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nonebot_plugin_mantou_affection-0.15.2.tar.gz | 2.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nonebot_plugin_mantou_affection-0.15.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.9 MB
Release files / nonebot_plugin_mantou_affection-0.15.2.tar.gz
| Download URL | nonebot_plugin_mantou_affection-0.15.2.tar.gz |
|---|---|
| Size | 2.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9e4129a186c270425c62756b9e41a7b366fd09de8c30f60cfdabc3e2e2ccd721
|
|
BLAKE2b-256 checksum How to use checksums |
22cd2d4c6385ff3cdd228742c67ac5f85953f578c0202a4997273686c74df50f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|
Release files / nonebot_plugin_mantou_affection-0.15.2-py3-none-any.whl
| Download URL | nonebot_plugin_mantou_affection-0.15.2-py3-none-any.whl |
|---|---|
| Size | 2.5 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c916470c816bf07f34ad94502a4b01fa1240e8cb5f8d84c4a26b28c443b74099
|
|
BLAKE2b-256 checksum How to use checksums |
2b79db22eab6f746dcc5bc8c3e969118658366d3fd05fe0ac4be843d22269196
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|