Skip to main content

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 的顺序,输家的好感度汇成奖池,平分给赢家和更接近的围观群友
  • 「馒头桃子问答」:群友发言时默认 1% 概率触发,@当前发言者出一道三选一;30 秒内被点名者必答、 其他群友可答,答对 +3、答错 -5、被点名者超时未答 -10,不看榜单也没有冷却
  • 「馒头群友问答」:群友发言时默认 0.05% 概率触发,@当前发言者出一道三选一;60 秒内所有人都可以回答, 答对 +2、答错 -3、被 @ 的触发者整局没答 -2,不看榜单也没有冷却
  • 「馒头命运齿轮转盘」:好感度 > 0 的群友发言时默认 0.1% 概率开一轮,触发者 60 秒内选择 运作或不运作;其他群友发送“参与”即可免费加入(含触发者最多 5 人,满员会提示),运作时每人独立 抽取随机表情、主动戳一戳或好感变化;不在免确认白名单的群友抽中表情时要先确认,开场消息会列出每项概率
  • SUPERUSER 的 /馒头强制事件 @群友:发一次活动列表,发起者 20 秒内回复 1~7 强制开一场活动, 绕过概率、好感门槛和功能开关,但保留群级互斥
  • 为其他插件提供好感上报与状态读取 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_CHANCE=0.01
MANTOU_AFFECTION_PK_LOW_STOCK_WARNING=100
MANTOU_AFFECTION_GROUP_QUIZ_CHANCE=0.0005
MANTOU_AFFECTION_GROUP_QUIZ_TIMEOUT=60
MANTOU_AFFECTION_WHEEL_CHANCE=0.001
MANTOU_AFFECTION_WHEEL_TIMEOUT=60
MANTOU_AFFECTION_WHEEL_MEME_CONFIRM_TIMEOUT=30
MANTOU_AFFECTION_MEMES_EXTRA_REPOS=true
MANTOU_AFFECTION_MEMES_BLOCKED_TERMS=

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_CHANCE 是「馒头桃子问答」的触发概率(默认 0.01,即 1%;0 即关闭), MANTOU_AFFECTION_PK_LOW_STOCK_WARNING 是问答题库余量提醒的阈值(默认 100)。触发概率可以用 /馒头问答概率 在运行时覆盖并持久化。

MANTOU_AFFECTION_GROUP_QUIZ_CHANCE 是「馒头群友问答」的触发概率(默认 0.0005,即 0.05%;0 即关闭), MANTOU_AFFECTION_GROUP_QUIZ_TIMEOUT 是答题窗口(秒,默认 60)。触发概率可以用 /馒头群友问答概率 在运行时覆盖并持久化。

MANTOU_AFFECTION_WHEEL_CHANCE 是「馒头命运齿轮转盘」的触发概率(默认 0.001,即 0.1%;0 即关闭), MANTOU_AFFECTION_WHEEL_TIMEOUT 是触发者的选择时限(秒,默认 60), MANTOU_AFFECTION_WHEEL_MEME_CONFIRM_TIMEOUT 是抽中表情后等待本人确认的时限(秒,默认 30)。 好感度大于 0 的群友发言时按触发概率独立掷一次,命中就 @ 他开一轮转盘;概率可以用 /馒头转盘概率 在运行时覆盖并持久化,与其它活动的概率互不影响。MANTOU_AFFECTION_MEMES_EXTRA_REPOS 控制是否自动下载 并挂载 meme-generator-contrib、meme_emoji、tudou-meme 三个外部表情仓库(默认 true,仅在安装了 [memes] 扩展时生效)。

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 即可(不用 @机器人),每人只算第一次; 发别的内容不算作答,会一直等到窗口结束。同一个群同时只有一个活动——随机事件、找字小游戏、馒头博弈、 随机问答和群友问答共用这个席位,上一次还没结束就不会再触发新的(这时新的小动作会回退成普通旁白)。

三个选项的顺序每次都会重新打乱,看起来都很合理,但只有最懂馒头心思的那个是最好的:最优 +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 列、答案位置覆盖每一行每一列;往数组里追加题目不需要改代码,追加后重启机器人即可生效。

和随机事件一样,同一个群同时只有一个活动(小动作升级来的答题事件、找字小游戏、馒头博弈、随机问答和 群友问答共用这个席位),上一个还没结束就不会再开新的,这时新的小动作会回退成普通旁白;窗口结束前的记录 不会因为有人答对而提前结束, 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。
  • 开局前会先占同群席位(和小动作答题、找字小游戏、随机问答、群友问答共用,本群已有活动时返回 False); 群消息触发时,依次判定找字小游戏、博弈、随机问答、群友问答、命运齿轮转盘和小动作,同一条消息最多出现 其中一种活动。
  • 窗口长度是 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 句互不重复;往数组里追加题目不需要改代码,追加后重启机器人即可生效。

馒头桃子问答

「馒头桃子问答」是非对抗的随机三选一问答。群友正常发言时按 MANTOU_AFFECTION_PK_CHANCE (默认 0.01,即 1%)独立判定,命中后 @ 当前发言者并只出一道题;不看好感度榜,也不要求好感度大于 0。

玩法

  • 触发:不需要主动发命令,也没有发起冷却。找字小游戏、馒头博弈、随机问答、群友问答、命运齿轮转盘和 馒头小动作按顺序判定,同一条消息最多触发一种;同群已有活动时也不会再开一局。

  • 作答:每次只有一道三选一题,窗口固定 30 秒。被 @ 的人必须回答,其他群友也可以回答;直接发 1、2、 3 即可,不用 @机器人。每人只算第一次作答,即使被点名者已经回答,也会等到 30 秒结束再统一公布结果。

    @桃友 🍑 桃子问答!
    桃子大量成熟的季节一般是?
    1. 深冬
    2. 夏天
    3. 初春
    被 @ 的群友必须作答,其他群友也可以回答;直接发送 1、2、3 即可(答题不用@)。
    答对 +3,答错 -5;被 @ 的群友超时未答 -10。请在 30 秒内作答。
    
  • 结算:每个实际作答者独立结算,答对 +3、答错 -5;被 @ 的人到时间仍未作答则 -10,答错不会重复算超时。 其他群友不答不受影响。结果会公布正确选项,并逐人提示好感度变化和当前好感度;好感度最低仍为 0。

    🍑 桃子问答!
    答案:2. 夏天
    @桃友 答对了,好感度 +3,当前 13
    @路人甲 答错了,好感度 -5,当前 15
    @路人乙 超时未答,好感度 -10,当前 10
    

SUPERUSER 可以用 /馒头问答概率 查看或修改触发概率,设为 0 即关闭;运行时覆盖会持久保存。

问答题库

题库是 nonebot_plugin_mantou_affection/resources/pk_questions.json,一个题目数组(也兼容带 questions 数组的对象),每题长这样:

{
  "questions": [
    {"text": "题干", "options": ["选项一", "选项二", "选项三"], "answer": 0}
  ]
}
  • text 是题干,options 必须正好三个非空选项,answer 是正确选项下标(0、1、2)。
  • 加载时逐题校验,坏题跳过并记一条 warning,其余照常使用;概率命中但题库已空时会提示 SUPERUSER 补题。
  • 每次从没出过的题里抽一道,抽到就立刻标记为已用并写进数据文件,重启也不会重复出题。
  • 余量低于 MANTOU_AFFECTION_PK_LOW_STOCK_WARNING(默认 100)时,会在结算后私聊提醒所有 SUPERUSER 补题 (同一次低余量只提醒一次,补题回升到阈值以上后重新计数)。SUPERUSER 也可以用 /馒头问答题库 查看余量。
  • 题库随插件一起打包(resources/pk_questions.json),正式题库共 10000 题:题干两两不重复、每题三个 互不相同的选项,正确选项落在 0、1、2 三个位置;往数组里追加题目不需要改代码,追加后重启机器人即可生效。

馒头群友问答

「馒头群友问答」是简化版的随机三选一问答。群友正常发言时按 MANTOU_AFFECTION_GROUP_QUIZ_CHANCE (默认 0.0005,即 0.05%)独立判定,命中后 @ 当前发言者出题;不看好感度榜,也不要求好感度大于 0。

玩法

  • 触发:不需要主动发命令,也没有发起冷却。找字小游戏、馒头博弈、随机问答、群友问答、命运齿轮转盘和 馒头小动作按顺序判定,同一条消息最多触发一种;同群已有活动时也不会再开一局。

  • 作答:每次只有一道三选一题,窗口由 MANTOU_AFFECTION_GROUP_QUIZ_TIMEOUT 决定(默认 60 秒)。 所有人都可以回答,直接发 1、2、3 即可,不用 @机器人。每人只算第一次作答,即使触发者已经回答, 也会等到窗口结束再统一公布结果。

    @群友 💬 群友问答!
    馒头翻了翻聊天记录,考考你们……
    哭诉眼泪没停过之后,对方只给了句什么评价?
    1. 只回了一句「不赖」
    2. 劝他别太难过
    3. 问他哭什么
    大家都可以回答,直接发送 1、2、3 即可(答题不用@),请在 60 秒内作答。
    答对 +2,答错 -3;被 @ 的触发者整局没答 -2(答错算已答,不叠加)。
    
  • 结算:每个实际作答者独立结算,答对 +2、答错 -3;被 @ 的触发者整局都没作答则 -2,答错不会重复 算未答。其他群友不答不受影响。结果会公布正确选项,并逐人提示“答对 / 答错 / 整局未答”、好感度变化和 当前好感度;好感度最低仍为 0。

    💬 群友问答!
    答案:1. 只回了一句「不赖」
    @路人甲 答对了,好感度 +2,当前 12
    @路人乙 答错了,好感度 -3,当前 7
    @群友 整局未答,好感度 -2,当前 8
    

SUPERUSER 可以用 /馒头群友问答概率 查看或修改触发概率,设为 0 即关闭;运行时覆盖会持久保存。

群友问答题库

题库是 nonebot_plugin_mantou_affection/resources/group_quiz_questions.json,格式与桃子问答题库完全相同: 一个题目数组(也兼容带 questions 数组的对象),每题包含 text、正好三个非空 options 和 0~2 的 answer 下标。

  • 加载时逐题校验,坏题跳过并记一条 warning,其余照常使用。
  • 每次从全部题目里随机抽一道,不标记已出,允许重复抽到同一题;因此没有余量提醒,也不需要 /馒头问答题库 那样的已用记录。
  • 题库随插件一起打包(resources/group_quiz_questions.json),正式题库共 10000 题:题干两两不重复、 每题三个互不相同的选项;往数组里追加题目不需要改代码,追加后重启机器人即可生效。

馒头命运齿轮转盘

「馒头命运齿轮转盘」是一次限时的集体抽奖。好感度大于 0 的群友正常发言时按 MANTOU_AFFECTION_WHEEL_CHANCE(默认 0.001,即 0.1%)独立判定,命中后 @ 触发者开一轮;不看好感度榜, 也不参与小动作的抽签。

玩法

  • 触发:不需要主动发命令,也没有发起冷却。找字、博弈、问答、群友问答、转盘和馒头小动作按顺序判定,同一条 消息最多触发一种;同群已有活动(含随机事件)时不会再开一轮。转盘进行时,与转盘无关的普通文本不会 被吞掉,仍按原样交给其它插件处理。

    @桃友 馒头命运齿轮转盘出现了!参与完全免费,含触发者最多 5 人。
    触发者请在 60 秒内发送“运作”或“不运作”(不运作:1/3 概率好感 +2,2/3 概率好感 -2);发送“运作”后还需在倒计时内回复“确认”才会开转。
    其他群友发送“参与”即可加入,触发者确认后立即截止,运作时每人独立抽取一次。
    转盘各项概率:
    · 馒头制作随机表情 20%
    · 馒头主动戳一戳:×1 12%、×3 8%、×5 5%、×10 4%、×20 1%
    · 好感增加:+1 7%、+2 6%、+3 5%、+5 4%、+10 3%、+20 1%
    · 好感减少:-1 7%、-2 6%、-3 5%、-5 3%、-10 2%、-20 1%
    

    开场消息会把每项概率完整列出,概率表改动时文案自动同步,群友不用猜。

  • 选择:触发者在 MANTOU_AFFECTION_WHEEL_TIMEOUT(默认 60 秒)内直接发送 运作 或 不运作 决定这一轮是否开转;只有被 @ 的触发者能选,其他人发这两个词只会被提示一句。

  • 二次确认:发送 运作 后馒头会再 @ 触发者确认一次——文案会写明“一旦确认其他群友就无法再参与”, 并提醒必须赶在倒计时结束前回复 确认,超时本轮自动取消、不会运作;确认前倒计时不暂停, 这期间其他群友仍然可以参与,触发者也可以改发 不运作 放弃。

  • 参与:等待选择(含等待二次确认)期间,其他群友发送 参与 即可免费加入,参与成功会有明确提示; 除触发者外每轮最多 4 名参与者(含触发者共 5 人),满员后再发 参与 会提示名额已满; 同一人重复参与、触发者自己发 参与 也都会有提示。触发者确认(或选择不运作)后不再接受新参与者。

  • 超时:60 秒内没有选择、或发了 运作 但一直没 确认,都整轮取消,不抽取任何结果,所有人好感不变。

  • 不运作:只结算触发者——1/3 概率好感 +2,2/3 概率好感 -2;其他参与者不共享这个结果。

  • 运作:触发者与每位参与者各自独立抽取一次完整转盘。各人的好感 增减、戳一戳次数和表情模板都独立随机、互不影响,可以出现完全不同的结果;结算顺序固定为触发者先、 参与者按加入顺序后。

  • 好感结算:转盘结果走管理员调整那条路径,不计入每日主动互动次数,也不受联动奖励 冷却影响;好感仍受全局最小值 0 和既有最大值约束,提示的是实际生效的变化。

  • 失败处理:抽中表情但素材或生成失败、主动戳一戳接口不可用时只提示失败,不会重抽, 避免用故障反复刷结果。

表情二次确认

转盘抽中表情(20%)时,馒头会先看这个人是否在「表情免确认白名单」里:

  • 白名单成员:保持原来的行为,直接制作并发送表情,不再询问。

  • 其他群友:先抽好具体模板,再发一条确认消息,列出表情名字(模板关键词)、种类标签和键名,并提示 表情内容可能引起不适。在 MANTOU_AFFECTION_WHEEL_MEME_CONFIRM_TIMEOUT(默认 30 秒)内回复 确认 才会用抽好的同一个模板制作;回复 不确认 或超时未回复都会跳过制作,并按 50% 概率 好感 +2、50% 概率好感 -2 结算,结果会 @ 本人告知当前好感。

    @群友 你抽中了表情「摸摸头」(种类:可爱、宠物,键名:petpet)。
    表情内容可能引起不适,请谨慎确认。
    请在 30 秒内回复「确认」或「不确认」。
    不确认或超时未回复,都按 50% 概率好感 +2、50% 概率好感 -2 处理。
    
  • 同一轮多个人抽中表情时,各自的确认流程并行独立,不会阻塞转盘结算,也不占用同群活动席位; 同一个人已有未决确认时又抽中表情,旧确认直接作废(不结算、不改变好感),以新的为准。

  • 确认后制作失败仍按原有失败文案处理(素材未就绪或生成失败),不会重抽。

  • 白名单由群友自助管理:发送 /馒头表情免确认 加入,再发一次退出;普通群友只能切换自己, 带参数或 @ 别人不会操作对方。SUPERUSER 可以额外用 /馒头表情免确认 @群友 或 /馒头表情免确认 QQ号 代管一位群友(一次只能一个目标,不带参数仍切换自己)。 白名单全局生效(不分群),保存在数据文件的 settings.meme_confirm_whitelist 里。

    已加入表情免确认白名单,以后转盘抽到表情将直接制作,不再询问(再发一次 /馒头表情免确认 可退出)
    

完整转盘概率

每次「运作」对每个人分别按下表抽取一次,权重合计 100%:

结果类别 具体结果 概率
表情 随机生成一个兼容表情 20%
主动戳一戳 戳 1 次 12%
主动戳一戳 戳 3 次 8%
主动戳一戳 戳 5 次 5%
主动戳一戳 戳 10 次 4%
主动戳一戳 戳 20 次 1%
好感变化 +1 7%
好感变化 +2 6%
好感变化 +3 5%
好感变化 +5 4%
好感变化 +10 3%
好感变化 +20 1%
好感变化 -1 7%
好感变化 -2 6%
好感变化 -3 5%
好感变化 -5 3%
好感变化 -10 2%
好感变化 -20 1%

汇总:表情 20%,主动戳一戳 30%,正向好感 26%,负向好感 24%;高额正负结果比低额结果更稀有。主动戳一戳 用的是 OneBot 的主动戳一戳接口,多次调用顺序执行并留轻微间隔;接口不可用时只提示失败,不会伪造成功文案。

SUPERUSER 可以用 /馒头转盘概率 0.1% 查看或修改触发概率,设为 0 即关闭;运行时覆盖会持久保存。

memes 表情联动(可选)

表情结果直接使用 nonebot-plugin-memes 同款底层生成器 meme-generator(0.1.x 版本,推荐 0.1.14), 不导入该插件的内部管理器,也不模拟群友发送 memes 命令,因此不会继承它的禁用名单、黑白名单或命令状态。 默认不安装也不影响插件导入、启动和其它玩法,需要表情结果时再装:

pip install "nonebot-plugin-mantou-affection[memes]"

注意:[memes] 扩展会把 meme-generator 固定到 0.1.x(外部表情仓库只兼容这个系列),已装 0.2.x 的环境会被 pip 自动降级;如果你同时使用 nonebot-plugin-memes,请按 meme_emoji 仓库的要求使用 nonebot-plugin-memes==0.7.12。

  • 生成时从 get_memes() 里筛选能由规定输入完整生成的模板:纯文字模板用群昵称,单人图片模板用参与者 头像,双人图片模板第一张用 bot 自己头像、第二张用参与者头像;需要更多图片或参数不兼容的模板不进候选 列表。每个参与者独立随机模板,图片生成在工作线程执行,不阻塞事件循环。

  • 素材不随 Python 包分发:字体和图片素材首次使用前需要补齐,第一次启动可能在后台下载数百 MB。插件 启动时会发起一次后台 meme_generator.download.check_resources() 检查,只发起、 不等待,不阻塞启动和事件处理;如果已加载的 nonebot-plugin-memes 自己会做启动检查,就复用它,不重复 发起下载。

  • 外部表情仓库自检测、自下载:除了内置表情,插件还会在启动时自动下载并挂载这三个扩展仓库 (与 meme-generator 官方 README 推荐的「其他表情仓库」一致):

    仓库以 zip 形式下载到本插件数据目录下的 meme_repos/,GitHub 直连失败时自动依次尝试多个镜像; 文件总量较大(数百 MB),只在首次或目录缺失时下载,下载完成后通过 load_memes 挂载,重启后直接复用 本地副本。某个仓库下载或加载失败只记日志,不影响内置表情和其它仓库。不需要扩展仓库时可以关闭:

    MANTOU_AFFECTION_MEMES_EXTRA_REPOS=false
    
  • 表情屏蔽词:内置一组库洛游戏相关屏蔽词(kurogames_ 统一前缀、「库洛」「战双」「帕弥什」「松伦」,以及 鸣潮角色名、昵称、阵营梗与模板的英文键名,如「今汐」「长离」「守岸人」「漂泊者」jinhsi camellya rover 等),命中模板键名或关键词的表情不会进入转盘候选,覆盖内置表情和全部外部仓库——外部仓库里 全部 117 个 kurogames_ 模板(含鸣潮、战双与库洛通用表情)默认都被屏蔽。想追加屏蔽词(逗号、顿号、 分号或空格分隔均可):

    MANTOU_AFFECTION_MEMES_BLOCKED_TERMS=原神,崩坏
    
  • 素材没下完时生成会失败,这次表情结果按未交付处理:只提示失败、不重抽, 其他参与者不受影响;随后会节流补检(默认 600 秒内只补一次),防止连续消息重复启动大量下载线程。 远程清单或单个文件下载失败也不影响 bot 运行,之后重启或补检会再次尝试,官方哈希校验会修复不完整文件。

SUPERUSER 强制事件

/馒头强制事件 @群友 是给 SUPERUSER 的调试与救场命令:馒头会发一张活动列表,发起命令的 SUPERUSER 在 20 秒内直接回复数字,就能强制开一场活动:

🎛️ 馒头强制事件(目标:桃友)
1. 馒头小动作
2. 随机事件
3. 找字
4. 馒头博弈
5. 桃子问答
6. 群友问答
7. 命运齿轮转盘
请在 20 秒内直接回复数字选择(只有发起命令的 SUPERUSER 有效)。
  • 强制:绕过概率、好感门槛和功能开关——小动作不看 MANTOU_AFFECTION_AMBIENT_ENABLED 与 MANTOU_AFFECTION_AMBIENT_PROBABILITY,好感度 0 的群友也能被点名;博弈不看 MANTOU_AFFECTION_BET_CHANCE 与 MANTOU_AFFECTION_BET_MIN_AFFECTION;找字、问答、群友问答、转盘 也都不看各自的概率。
  • 群级互斥仍然保留:本群还有待结算的活动时,命令直接提示不能开新一轮;命令发出后、选择之前的 20 秒内如果有别的活动抢先开局,这次强制事件也会提示并取消。
  • 只有发起者能选:其他人回复数字不影响选择;超时或回复的内容不是 1~7 会直接取消,不改变任何好感度。
  • 各选项的落地方式:
    • 「馒头小动作」只发一句小动作旁白(不会升级成随机事件),文案仍按目标的好感阶段抽取;
    • 「随机事件」用现有的事件协调器出一道三选一(好感高时可能是闹别扭六选一),答题与结算和平时完全一样;
    • 「找字」贴一张找字方阵,窗口内抢答;
    • 「馒头博弈」仍要从最近说过话的群友里抽另外两位对手,凑不齐两位就不开局;
    • 「桃子问答」和「群友问答」各出一道三选一,后者不标记已出;「命运齿轮转盘」开一轮转盘, 运作/不运作与免费参与规则照旧。
  • 强制开出来的活动,好感结算走和平时同一条路径(管理员调整,不受每日获取总上限约束)。

大型文案库

内置文案位于 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 即关闭;持久保存)
/馒头问答概率 1% SUPERUSER 群聊/私聊 查看或调整随机问答触发概率(默认 1%,0 即关闭;持久保存)
/馒头问答题库 SUPERUSER 群聊/私聊 查看问答题库还剩多少题
/馒头群友问答概率 0.05% SUPERUSER 群聊/私聊 查看或调整群友问答触发概率(默认 0.05%,0 即关闭;持久保存)
/馒头转盘概率 0.1% SUPERUSER 群聊/私聊 查看或调整命运齿轮转盘的触发概率(默认 0.1%,0 即关闭;持久保存)
/馒头表情免确认 [@群友/QQ号] 群员 群聊 加入或退出表情免确认白名单,再发一次切换;普通群友只能切换自己,SUPERUSER 可 @一位群友或填写 QQ 号代管(持久保存)
/馒头强制事件 @群友 SUPERUSER 群聊 发活动列表,发起者 20 秒内回复 1~7 强制开一场活动

插件联动

插件注册了 NoneBot run_postprocessor。其他插件的 Matcher 成功执行后,如果:

  1. 来源插件出现在奖励表中;
  2. 事件来自群聊;
  3. Matcher 是命令、正则、完整匹配、关键词、开头或结尾匹配之一;
  4. 本次执行没有抛出异常;
  5. 没有触发该插件的联动冷却或每日上限;

则按配置增减发起者的好感度,并在好感真正变化时补一条轻量提示(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,不会扣好感;这个 +1 受每日获取总上限约束,额度用完时 delta=0,文案会补一行 「今天的好感已经拿满啦,明天再来吧。」
  2. text 在 delta != 0 时末尾会追加一行「好感度 +1,当前 12」这样的提示;delta=0 时只有文案本身。
  3. 有 MANTOU_AFFECTION_POKE_EVENT_CHANCE(默认 0.05%)的概率戳出一次随机事件:这时 text 就是事件消息 本体(不带戳一戳文案,也不 @任何人),事件消息发到群里后任何人发 1/2/3 作答,窗口结束统一结算。 抽题和普通小动作一样看触发者的好感阶段,所以亲密关系下也可能戳出闹别扭题(消息前缀是 「💢 馒头闹别扭了!」)。结算的翻倍规则:普通题的负向选项 -4、超时 -10;闹别扭题的比例扣分算完后再翻倍 (好感 200 时最重扣 -30 → -60、超时 -20 → -40),正向奖励(普通题 +1 / +2,闹别扭题 +2 / +5)不变。
  4. 本群已经有未结束的事件、事件库为空,或调用时没有传 send,都会退化成普通戳一戳文案(+1 照常入账)。
  5. 随机事件没触发时,还会再按 MANTOU_AFFECTION_POKE_FIND_CHAR_CHANCE(默认 0.05%)掷一次找字小游戏:命中时 text 是找字方阵本体(同样不 @任何人,结算走 send),方阵和作答规则见上面的「找字小游戏」。
  6. 文案统一做 {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_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.21.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nonebot-plugin-mantou-affection 0.21.1
File Size Uploaded Provenance
nonebot_plugin_mantou_affection-0.21.1.tar.gz 3.2 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for nonebot-plugin-mantou-affection 0.21.1
File Interpreter ABI Platform
nonebot_plugin_mantou_affection-0.21.1-py3-none-any.whl Python 3 none any Details

Total release size: 6.5 MB

Release files / nonebot_plugin_mantou_affection-0.21.1.tar.gz

Download URL nonebot_plugin_mantou_affection-0.21.1.tar.gz
Size 3.2 MB
Tags Source
SHA-256 checksum
How to use checksums
b14a67e3f4122b09f633787116798a7491888524350f1410a03c082ddb7b4c0f
BLAKE2b-256 checksum
How to use checksums
8dc71685ba38de0b89b1417dd67961caf4f9373e4daa950cfd11875e9e991f40
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release files / nonebot_plugin_mantou_affection-0.21.1-py3-none-any.whl

Download URL nonebot_plugin_mantou_affection-0.21.1-py3-none-any.whl
Size 3.3 MB
Tags Python 3
SHA-256 checksum
How to use checksums
cdc957281557735cffa96d5c791e5d338330ad11bef72f5827b606619a21e180
BLAKE2b-256 checksum
How to use checksums
f3f4ff657df1b14ec57de126cd74b941da98f8b637b5eaa53b4fb1ddb2d6eddd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Security for nonebot-plugin-mantou-affection 0.21.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.21.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page