ChatLuna 群聊会话系统权限与模型踩坑配置指南
最近重新折腾了一下 Koishi 和 ChatLuna。我的需求其实很简单:把机器人放在群里,所有人共用同一段聊天记录,并且任何群友都可以执行 chatluna.new 开一个新会话。然而自从chatluna放弃了房间系统转而使用了会话系统之后,变得非常难用,默认管理员执行 new 没有问题,普通群友执行就会报错:
错误码 413:当前路由要求管理员权限才能管理会话。
一开始我以为是 Koishi 的指令权限问题,于是在指令管理里找了半天。后来又发现,把群聊模式改成“成员独立会话”之后,普通群友确实可以 new 了,但是每个人聊的内容也完全分开了。能执行是能执行了,大家却不在同一个聊天里,这显然不是我要的效果。(神秘权限问题+1)
而我手上正好有一份从旧版本 ChatLuna 迁移过来的数据库。同样使用新版本插件,换上这份旧数据库之后,群友就可以一起聊天,并且随便执行 new。然而,旧版本数据库在模型同步这一块又有问题,始终带着 Bug 运行实在难以维持,因此还是下定决心把这个问题彻底弄清楚。
虽然官方提供了会话相关文档,但只能说这份文档写得着实有点一坨:不少地方说得不清不楚,部分内容和当前源码还完全对不上。最后没办法,只能试着从源码层面读一读 ChatLuna 这套会话权限到底是怎么回事。
一、为什么普通群友执行 new 会报 413
ChatLuna 现在的群聊会话主要涉及两个配置:routeMode 和 manageMode。
routeMode 决定大家会进入哪一条会话路由,常用值是:
shared 群内所有成员共用一条路由
personal 群内每个成员使用自己的路由
manageMode 决定谁可以管理这条路由里的会话,常用值是:
admin 只有管理员可以管理会话
anyone 所有人都可以管理会话
ChatLuna 在群聊中的默认路由模式是 shared,也就是大家一起聊天。这个设置本身没有问题。问题在于,没有匹配到其他规则时,manageMode 默认会被解析为 admin。因此,默认组合实际上是:
routeMode = shared
manageMode = admin
在这种组合下,群里的聊天记录虽然是共享的,但新建、切换等会话管理操作只允许管理员执行。当前版本创建新会话时的权限判断,大致可以简化为:
if (
manageMode === 'admin' &&
routeMode !== 'personal' &&
当前用户不是管理员
) {
返回错误码 413
}
所以普通群友执行 chatluna.new 时,三个条件正好全部命中:
管理模式是 admin
当前是 shared 路由
执行者不是管理员
于是就出现了 413。这个默认设置其实也可以理解。因为在共享路由里执行 new,影响的并不只是执行命令的那一个人,而是整个群。任何人执行一次,全群当前使用的会话都会被切走。ChatLuna 默认不开放,大概也是为了防止群友随手把大家正在聊的上下文清掉。
二、开发者预期的解决办法
最简单的解决办法,是把 ChatLuna 设置中的“群聊场景默认路由模式”改成“成员独立会话”,也就是:
defaultGroupRouteMode: personal
改完之后,普通群友确实可以执行 new。原因是 ChatLuna 对 personal 路由跳过了上面的共享会话管理员检查。但是,这个方法同时改变了会话的路由方式:
群友 A -> A 的会话
群友 B -> B 的会话
群友 C -> C 的会话
A 执行 new 只会新建 A 自己的会话,B 和 C 仍然停留在各自原来的会话里。大家看起来都在同一个群里和机器人说话,实际上背后完全是三段互不相干的上下文,无法实现群内共享聊天的效果。
吐槽:我们之间已经有了一层可悲的厚障壁了.jpg
三、真正需要修改的是 manageMode
既然 shared 不能改,那剩下的办法就是把:
manageMode = admin
改成:
manageMode = anyone
最终需要的组合为:
routeMode = shared
manageMode = anyone
allowNew = true
其中,allowNew 是另一个开关,用来决定当前路由是否允许创建新会话。它和 manageMode 并不是同一个东西。
简单来说:
allowNew = true
表示“新建会话这个功能没有被禁用”
manageMode = anyone
表示“普通群友有资格操作这个功能”
如果只设置 allowNew = true,但管理模式仍然是 admin,普通群友还是会报 413。
然而真正坑爹的是,开发者没有为 manageMode 提供任何可以直接操作的设置选项,只能通过修改数据库来添加规则。
强烈吐槽:什么烂尾功能!你倒是开发好再上线啊。
四、重点:添加一条 anyone 约束
ChatLuna 的这些规则保存在 chatluna_constraint 表中。
旧版本数据库迁移之后之所以可以让所有人执行 new,是因为我之前做迁移时已经写入过一条类似下面的兼容规则:
manageMode = anyone
而全新安装生成的数据库中没有这条规则,所以最终会回退到默认值 admin。如果让所有群都采用这套规则,所以添加的是一条全局约束,核心内容如下:
name = allow-anyone-manage
enabled = true
priority = 100
manageMode = anyone
allowNew = true
platform、guildId、channelId 等匹配字段全部留空时,这条规则会匹配全部会话场景。如果只准备对某一个群开放,建议填写对应的平台和群聊匹配字段,不要直接全局放开。例如,概念上可以设置为:
name = allow-group-members-manage
enabled = true
priority = 100
platform = 对应平台
guildId = 对应群聊标识
manageMode = anyone
allowNew = true
不同适配器对 guildId 和 channelId 的填写方式可能不同,可以先通过 Koishi 的调试工具确认事件中的实际字段。这里不要照抄别人的群号,因为当然不可能有用(废话)。
如果只想修改已经存在的规则,也可以找到目标约束,然后把它的 manageMode 改成 anyone。
规则保存后,重启 ChatLuna 或 Koishi,然后在群里测试:
chatluna.new
不出意外的话,普通群友就可以正常创建新会话,并且全群会一起切换过去。此时的实际效果是:
群友 A 执行 new
↓
共享路由创建一个新会话
↓
群友 A、B、C 后续都进入这个新会话
五、顺便说一下管理员到底怎么判断
ChatLuna 判断管理员时,会优先检查专用权限:
chatluna:admin
如果没有通过专用权限检查,还会继续检查 Koishi 用户的 authority。达到管理员等级的用户,同样会被视为管理员。
所以另一个简单粗暴的解决办法,是把所有群友的 authority 都提高,或者给所有人授予 chatluna:admin。但是不建议这么做。提高 Koishi 的全局用户权限,可能会连带开放其他插件的管理命令。只是为了让群友执行一个 chatluna.new,结果顺手把一堆后台能力也送出去了,权限范围明显过大。manageMode = anyone 只会改变 ChatLuna 对应规则下的会话管理行为,相对来说更加准确,也更符合最小权限原则。
六、迁移旧数据库后,为什么修改默认模型没有效果
权限问题解决后,接下来就是第二个问题。使用旧数据库迁移到新版本 ChatLuna 后,群友确实都可以执行 new,但是在 ChatLuna 设置页修改默认模型却没有效果。无论怎么改,新会话仍然使用迁移前保存的旧模型。(神秘模型问题+1)
研究代码之后发现,ChatLuna 选择模型时,大致按照下面的优先级进行:
fixedModel
↓
当前会话的 conversation.model
↓
约束中的 defaultModel
↓
ChatLuna 设置页中的 defaultModel
也就是:
fixedModel > conversation.model > constraint.defaultModel > config.defaultModel
设置页里修改的是最后一项 config.defaultModel,优先级最低。旧数据库迁移过来的会话,通常已经在 chatluna_conversation.model 中保存了明确的模型名称。只要这个模型仍然有效,ChatLuna 就会继续使用它,而不会退回设置页中的默认模型。
更容易让人迷惑的是,执行 chatluna.new 时,新会话还可能参考当前路由中已有会话的模型。于是就出现了下面的情况:
设置页修改默认模型
↓
当前共享会话仍保存旧模型
↓
执行 chatluna.new
↓
新会话继续使用旧模型
看起来像是设置页失效了,其实只是已有会话保存的模型优先级更高。
七、正确切换已有会话的模型
如果只是想修改当前共享会话使用的模型,应该直接切换当前会话模型:
chatluna.use.model 平台名/模型名
例如:
chatluna.use.model example/example-model
切换成功后,当前会话的 conversation.model 会被更新。此时再创建新会话,后续会话就不会继续继承原来迁移进来的旧模型。如果希望某个群永远强制使用指定模型,可以让管理员设置路由规则:
chatluna.rule.model 平台名/模型名 -f
这里的 -f 表示固定模型,对应 fixedModel。它的优先级最高,即使会话中保存了其他模型,也会被强制覆盖。
如果只想设置默认值,而不强制覆盖已有会话,可以不使用 -f。需要清除规则时,可以使用当前版本提供的 --clear 参数:
chatluna.rule.model --clear
需要注意的是,一旦设置了 fixedModel,普通群友即使拥有会话管理权限,也无法自由切换到其他模型。因此,是只修改当前会话,还是给整个路由锁定模型,需要根据实际使用方式选择。
八、是否可以直接修改数据库中的模型
当然也可以。
当前会话使用的模型保存在 chatluna_conversation 表的 model 字段中。理论上可以直接批量替换旧模型:
UPDATE chatluna_conversation
SET model = 'example/example-model'
WHERE model = 'example/example-old-model';
但是,不建议无脑执行下面这种更新:
UPDATE chatluna_conversation
SET model = 'example/example-model';
因为数据库里可能同时存在私聊、其他群聊、归档会话和特殊用途会话。全部覆盖之后,原本故意使用不同模型的会话也会一起被改掉。
真要批量处理,最好根据 bindingKey、原模型名称和会话状态精确筛选,并且提前备份数据库。能通过 ChatLuna 命令正常切换的话,还是优先使用命令,至少不容易把其他会话一起扬了。
九、总结
这次问题的核心,是 ChatLuna 把“大家是否共用同一条会话”和“谁有权管理这条会话”拆成了两个不同的配置:
routeMode 决定大家是否进入同一条会话路由
manageMode 决定谁有资格管理这条会话路由
所以,如果想让所有群友一起聊天,并且都能自由执行 chatluna.new,正确的配置应该是:
routeMode = shared
manageMode = anyone
allowNew = true
然而由于chatluna的雷霆设置,因此必须要直接在数据库中新增manageMode管理
至于旧数据库迁移之后默认模型改不动,则是另一个独立问题:迁移会话已经保存了自己的 conversation.model,它的优先级高于设置页中的 defaultModel。使用下面的命令更新当前会话模型即可:
chatluna.use.model 新模型
本文基于 ChatLuna 1.4.0 Alpha 系列整理。ChatLuna 仍在快速更新,后续版本的命令、数据库字段及默认行为可能发生变化,请以实际安装版本为准。修改数据库前务必备份,并避免公开包含群号、用户 ID、聊天记录或接口密钥的生产数据库。
