指令管理的优化建议

话说评分的依据是什么)

4 个赞

请在官方文档进行搜索。

3 个赞

支持喵

4 个赞

来自 2024 年的回旋镖:

  • 下个版本将支持禁用原生别名的功能(你甚至可以把所有名字全删了)
  • 关于上面提到的无法触发的问题,会采用「原名可以被插件调用但不能被用户触发」的处理思路
7 个赞

快把香槟全部开了!
对了,如果原名可以被插件调用的话,那要怎么解决冲突的问题呢

4 个赞

倆打一架,誰贏給誰

4 个赞

原名不能冲突,别名也不能冲突。

原名无法修改,因此两个插件的指令如果原名相同,那么就只能启用一个。

别名可以禁用或者修改,因此两个插件的指令如果别名相同(但是原名不同),你可以先启用一个插件,然后把冲突的别名禁用掉,再启用另一个插件。

4 个赞

原名不能冲突只是为了session.execute吗,还是有其他更深层的原因

2 个赞

无论是原名不能冲突还是别名不能冲突,都是因为如果两个插件别名冲突那么同时安装行为会不确定。

Koishi 希望相同的配置文件产生相同的行为。

在此之上,原名不能冲突有着更多意义:

  1. 如果原名冲突,那么无法基于 session.execute 实现调用指令
  2. 如果原名冲突,那么无法在本地化中自定义输出文本
  3. 如果原名冲突,那么无法在指令管理中设置权限等属性

原名是指令的唯一标识符,如果冲突了一切基于此的设置都做不了。

6 个赞

明白了,谢谢海胆

3 个赞

明白了,谢谢海胆

3 个赞

明白了,谢谢海胆

2 个赞

已更新:

5 个赞

我宁愿同时触发多个指令,把选项、内容都传入每一个注册的指令,对每一个指令都进行权限的判断。

Shigma 提到的都是一些技术上的问题,也许积重难返不好更改了。

3 个赞

这样就会导致:

依据你的愿望设想以下场景

场景 1

  • 插件 A 注册了指令 foo,返回 乌拉!,插件 B 同样注册了指令 foo,返回 foofoo
  • 此时,插件 A 与 B 同时启动,并且有一个插件 C 通过 session.execute 调用了指令 foo 并期待返回 乌拉!

你觉得该怎么办呢?

场景 2

  • 这是插件 A 的本地化定义:
foo:
 messages:
   return: 乌拉!
  • 这是插件B 的本地化定义:
foo:
 messages:
   return: foofoo

如果在同时安装并启用了插件 A 与 B 情况下调用了指令 foo,应该返回哪一个?

场景 3

当某个地方禁止发送 乌拉!,Bot 维护者为了机器人的安全,在同时安装并启用了插件 A 与 B 情况下试图禁止因为 foo 而触发 乌拉!,但是禁止了 foo 指令之后 foofoo 也一起不允许了,该怎么办呢?


这不是技术上的问题,也不是积重难返不好更改,指令就应当是唯一的

双胞胎长得再一样也得有不一样的身份证号码,这里的指令就是这个身份证号码。

当两个插件的指令重复时最佳解决方案是两个开发者打一架,谁赢算谁的。

6 个赞

这个比喻不太恰当,因为一个国家内也有很多重名的人,人名是人名,身份证号是身份证号。

现在的做法就相当于国家给国民任选身份证号,谁不想有个精巧的身份证号呢?

同一个插件能有多份配置,同一个事件名能有多个 listener,同一个指令名为什么不能有多个 action ?

可以给指令相关的函数如 ctx.command session.execute 都提供一个 multiple 选项。比如对于 execute 如果使用了这个选项则执行所有同名指令并返回一个结果数组;如果没有这个选项则执行第一个注册或最后一个注册的指令;如果想要执行某个特定的指令,应找出它的 ID。

在控制台的指令管理界面,每个指令指出定义它的多个插件,对于同名的多个指令,可以独立地对其中某个指令进行禁用、权限管理、本地化等。

这样对你提到的每个场景都有解决方法。

3 个赞

这个设计很愚蠢。 唯一标识符应具有不易发生冲突的属性。

愚蠢的是我自己。

3 个赞

先逐个回答一下:

跟人名有啥关系?这句意思很简单:即使是双胞胎身份证号也不一样。

所以鼓励使用复杂的名称注册指令,并让用户自行在 commands 中配置别称

  • 同一个插件只能有一个配置。多个同样的插件配置各自独立且拥有不同的依赖树
  • 事件与订阅者之间是不存在任何关联的,事件的广播是无状态的,无论订阅者有多少,0 到无限个,都与事件广播无关
  • 这个跑题了但还是回答一下:同一个指令当然可以有多个 action,只要符合“中间件”模型,但是你能保证插件市场 1000 多个插件的每一个 action 都是 return next() ?

再给一个场景 4:

在讲述场景前,提前告知的特性:

  • koishi 是异步加载,且插件加载不存在优先级
  • koishi 仅通过依赖关系确定加载顺序
  • ctx.command 本质上是在向一个指令中间件注册指令
    • 中间件的模型来自 Koa ,自行查找资料

有两个插件:

  1. 这是一个游戏查询插件,注册了名为 lc 的指令。
    描述:load card,用于加载用户卡牌
    功能:将用户卡牌从网站加载,并以 hash(username + cardname) 方式保存这些卡牌到 /data/cache
    指令返回:操作成功

  2. 这是一个清理插件,注册了名为 lc 的指令。
    描述:little cache,用于清理缓存
    功能:清理 /data/cache 目录的缓存文件
    指令返回:操作成功

用户经历了这些:

  1. 发现游戏查询插件有 bug:不会对 cache 目录下的过期卡牌文件进行清理,导致磁盘占用过多
  2. 发现有一个清理插件,可以清理 cache
  3. 使用清理 cache 指令 lc

此时应当怎么做?

  • 如何在相同输出结果下保证预期动作?
  • 如何找出它的 ID?
  • 靠什么保证了执行顺序不会是 缓存文件 → 清除缓存

如果你觉得这是场景举例本身有问题,那我觉得没有继续讨论下去的必要了,因为避重就轻和偷换概念不是一个好的讨论。


一言以蔽之:保证指令唯一性是架构效率、执行效率、开发效率与用户体验的平衡点。

当你想要重复指令时带来的后果是灾难性的:

  • 无法保证不同操作过程但相同返回的结果符合预期
  • 插件开发者在使用 session.execute 时有更大的编写负担:因为两个插件同一个指令返回是一样的,但执行带来的后果不一样

上述后果只是目前想到的冰山一角,重复指令我想不出任何优点。

只允许唯一指令的情况下,用户无需再记忆一个出现在指令周围的随机id,koishi 不需要为保证预期行为而额外增加指数级代码量,其他插件开发者不需要因为多个插件不同行为而产生更多的心智负担。

所以,我就明说了,偷懒使用各种简单至极且在不同类型下都非常容易撞车的常见指令名称的开发者就是懒狗,是傻逼,是一个及其不负责任的开发者。


如果你依然坚持,那么请:

  • fork koishijs/koishi 仓库
  • 在 internal/command 中加入你自己的逻辑
  • 自己维护这一份 fork

这是开源软件,欢迎另起炉灶。

8 个赞

所以说啦,回归原始,指令冲突了让俩开发者打一架就好辣()

4 个赞

这本身就是开发者间自己解决的事情,不需要也不应该浪费 koishi 产能在这种无意义的事情上。

4 个赞