wechaty/wechaty

Wechaty Plugin Support with Kickout Example

huan opened this issue · 13 comments

huan commented

Middleware is computer software that connects software components or applications. The software consists of a set of services that allows multiple processes running on one or more machines to interact.
Wikipedia

See also: What is middleware exactly?

A Purpose from @Gcaufy

Yesterday, in our contributor group, @Gcaufy suggested that it would be great to add supporting of middleware to the Wechaty ecosystem, like the following usage:

有没有人把 踢人那个做成通用组件。。。那个很实用呀

wechaty.use(KickoutPlugin({
  room: 'RoomName',
}));

然后这个房间就有踢人功能了。

I feel that it is a Brilliant idea!

So how about we design a middleware system like this:

Wechaty.use(middleware: WechatyMiddleware)

type WechatyMiddleware = (this: Wechaty) => void

class Wechaty {
  public use (middleware: WechatyMiddleWare) {
    middleware.apply(this)
  }
}

const kickoutPlugin = (options = {}) => {
  const roomTopic = options.roomTopic
  return function (this: Wechaty) {
    this.on('message'), message => {
      if (message.room()) && message.room().topic() === roomTopic) {
        if (message.mentionSelf()) {
          // check vote
          message.room().del(...)
        }
      }
    })
  }
}

const wechaty = new Wechaty()
wechaty.use(kickOffPlugin({ roomTopic: 'Test Room' }))

Any comments about this design will be welcome.

P.S. The Kickout Feature was originally introduced from the PR add vote manager to manage vote message in room #4 authored by @windmemory

Links

这个想法很好。

为了让中间件不具备恶意攻击能力,可以采用vm的方式。可以制定一个规范,哪些是可以使用的,以及如何规范的使用。

同时,为了让中间件更具有通用性,似乎可以提供一个可配置的接口。

大家看看怎么做,在做好可扩展性的同时又保证安全性。

huan commented

It seems that our Plugin System will be ready soon! (thanks @Gcaufy!)

I'd like to write the following plugin for Wechaty as it's my favorite one:

The DingDong Plugin

export const dingDong = (options: {}) => async (wechaty: Wechaty) => {
  wechaty.on('message', message => {
    if (message.type() !== Wechaty.Message.Type.Text) { return }

    if (/^ding$/i.test(message.text())) {
      await message.say('dong')
    }
  })
}

In the future, we can add an option for our lovely DingDong Plugin so that we can control it, for example, only can be triggered in a specific room:

wechaty.use(dingDong({
  roomTopic: 'My Room'
}))

That's it!

What Plugin Feature do you want most? Please feel free to let us know by commenting on this issue!

huan commented

Wow!

first init
@Gcaufy
Gcaufy committed on Dec 9, 2017

What a historic repo!

huan commented

Thanks for this great new plugin feature created by @Gcaufy , our wechaty-plugin-contrib has been published!

https://github.com/wechaty/wechaty-plugin-contrib

With the power of the Wechaty Plugin, we can rewrite our ding-dong-bot.ts as the following:

import { Wechaty } from 'wechaty'

import {
  DingDong,
  EventLogger,
  QRCodeTerminal,
}             from 'wechaty-plugin-contrib'

const bot = new Wechaty({
  name : 'ding-dong-bot',
})

bot.use(
  QRCodeTerminal(),
  DingDong(),
  EventLogger([
    'error',
    'login',
    'logout',
    'message',
  ]),
)

bot.start()
  .catch(async e => {
    console.error('Bot start() fail:', e)
    await bot.stop()
    process.exit(-1)
  })

With the three plugins we created today:

Plugins Contrib

  1. DingDong - Reply dong if bot receives a ding message.
  2. EventLogger - Log Wechaty Events for "scan" | "login" | "message" ... etc.
  3. QRCodeTerminal - Show QR Code for Scan in Terminal

DingDong

  • Description: Reply dong if bot receives a ding message.
  • Author: @huan - Huan LI (李卓桓)
import { DingDong } from 'wechaty-plugin-contrib'

const options = {
  at   : false,   // default: false - Only react message that mentioned self (@) in Room
  dm   : true,    // default: true - React to Direct Message
  room : true,    // default: true - React in Rooms
}

wechaty.use(DingDong(options))

EventLogger

  • Description: Log Wechaty Events: "dong" | "message" | "error" | "friendship" | "heartbeat" | "login" | "logout" | "ready" | "reset" | "room-invite" | "room-join" | "room-leave" | "room-topic" | "scan"
  • Author: @huan - Huan LI (李卓桓)
import { EventLogger } from 'wechaty-plugin-contrib'
wechaty.use(EventLogger(['login', 'logout', 'message']))

QR Code Terminal

  • Description: Show QR Code for Scan in Terminal
  • Author: @huan - Huan LI (李卓桓)
import { QRCodeTerminal } from 'wechaty-plugin-contrib'
const options = {
  small: false,   // default: false - the size of the printed QR Code in terminal
}
wechaty.use(QRCodeTerminal(options))
huan commented

@Gcaufy Should we consider a situation that two plugins need to communicate between them?

For example:

  • PluginA is for identifying whether a message in the room is a spam
  • PluginB is for kicking out a specific user which be flagged by other plugins

And if there are some use cases of this, could our plugin system be able to support this?

huan commented

It seems that we need to support the async plugin method.

For example, if I have a plugin which I want to use an async function in it:

function AsyncPlugin (): WechatyPlugin {
  return async (wechaty: Wechaty) => { 
     const json = await fetch(url)
     // We will use json at here.
  }
}

Then we will need to change the Wechaty Plugin API Interface:

export interface WechatyPlugin {
-   (bot: Wechaty): void
+   (bot: Wechaty): void | Promise<void>
}

@Gcaufy Could you please take a look at it, and let me what you think? Thanks!

For the example you gave here. I think this will do the same.

function MyPlugin (): WechatyPlugin {
  return (wechaty: Wechaty) => {
    fetch(url).then(() => {
       wechaty.on();
    });
  }
}

The plugin himself is not async function. but he also can do synchronized thing inside. which mean they both can do the same thing.
So I'm not sure what need to be synchronized, if it's the logic inside, then it's supported already. If you need the plugins installed synchronized, then it's not supported. but in this case, I guess you have to use rx-queue to make all plugins they were in one queue.

huan commented

Yes you are right, for the above code, the functionality is the same.

I think the current design is good because the plugin can do any async task inside without care about the outside.

Only one more thing is: the plugin will encapsulate all the operations inside, the outside will not be able to know if there are any errors inside. And I feel this is ok because the plugin can emit error event to Wechaty whenever they want.

huan commented

Good news: our Java Wechaty starts supporting the Plugin too!

See: wechaty/java-wechaty#32

huan commented

The Python and Go team have a great design discussion on the plugin system at wechaty/python-wechaty#70

huan commented

Close this issue because the plugin system has been fully implemented.