swiftui-shapes icon indicating copy to clipboard operation
swiftui-shapes copied to clipboard

【RFC】Wechaty support middleware.

Open Gcaufy opened this issue 3 years ago • 15 comments

问题

Wechaty 已经开发过 plugin 机制,参考:

  • https://github.com/wechaty/wechaty/issues/1939

但 plugin 的粒度还是相对较粗,导致不同的 plugin 之间还是存在许多重复代码。

vote-out 插件为例,假设我们有个需求,需要写个 vote-up 来记录每天群内谁被点贊最多,此时就会发现 vote-out 的所有过滤代码都得原封不动的拷贝至 vote-up 中,然后再完成 vote-up 的真实逻辑。 所以根本原因是 plugin 中的逻辑无法被更小粒度的抽象和复用,

思考

很早之前也粗略的思考过这里的问题,但改造成本较高,且与历史API存在兼容性问题,但最近又思考了一下,发现还是可以解决的。 可以扩展一种针对事件维度的 middleware 机制(洋葱模型)进行更小粒度的抽象。

示例代码如下

在现有模式下,如果要完整类似功能,需要这么写。

// room a 需要 vote-out 功能
bot.on('message', async (message) => {

  const room = message.room()

  if (!room) return 
  if (message.type() !== type.Message.Text) return 
  if (!matchers.roomMatcher('room a')) return

  console.log('do vote-out action');
});
// room b 需要 vote-up 功能
bot.on('message', (msg) => {

  const room = message.room()

  if (!room) return 
  if (message.type() !== type.Message.Text) return 
  if (!matchers.roomMatcher('room a')) return

  console.log('do vote-up action');
});

设计事件维度中间件能力后,可以这样写。

// 抽象通过能力为中间件,由中间件控制事件响应
const filterMiddleWare = (options) {
  return async (message, next) => {

    const room = message.room()

    if (!room) return 
    if (message.type() !== option.type) return 
    if (!matchers.roomMatcher(option.room)) return

    await next();
  }
}

// 只有 room a 中会响应, vote-out 事件
bot.on('message', 
  filterMiddlerWare({ type: type.Message.Text, room: 'room a'}),
  (message) => console.log('do vote-out action')
);

// 只有 room b 中会响应, vote-up 事件
bot.on('message', 
  [ filterMiddlerWare({ type: type.Message.Text, room: 'room a'}) ],  // support middleware array.
  (message) => console.log('do vote-up action')
);

对于现有插件,我们也可以扩展 use 方法在执行前去装载中间件

// VoteOut 插件只对 room a 生效
bot.use(VoteOut({ /* options */ }), { message: [ filterMiddleWare({ room: 'room a'}) ] })

这样,就能无缝的兼容原来的 API 和 插件能力啦 。

未来插件也可以做得更轻,只专注于功能本身,而不用去写一堆的 if (xxxx) return 啦。 而同时,插件本身也可以通过拼接一堆 middleware 来完成一个大的功能插件。

变更

on (event: WechatyEventName, listener: (...args: any[]) => any): this
on (event: WechatyEventName, middlewares: WechatyMiddleWare | WechatyMiddleWare[], listener: (...args: any[]) => any): this


use(...plugins, option: WechatyPluginMiddleWare);

Related issues

  • https://github.com/websockets/ws/issues/1818

Gcaufy avatar Oct 26 '21 14:10 Gcaufy