Giter Club home page Giter Club logo

puppet's Introduction

wechaty-puppet

Powered by Wechaty NPM Version npm (tag) TypeScript Linux/Mac Build Status ES Modules

chatie puppet

Picture Credit: https://www.shareicon.net

Abstract(Base) Class of Puppet Providers for Wechaty Framework.

This module is part of the Wechaty Framework SDK.

Learn more at:

  1. Wiki: https://github.com/wechaty/wechaty/wiki/Puppet
  2. Issue: wechaty/wechaty#1167

Documentation

Wechaty Puppet Provider Interface Documentation can be found at https://wechaty.github.io/wechaty-puppet/typedoc/classes/puppet.html

Automatica Generated by TypeDoc

Example

PuppetMock: https://github.com/wechaty/wechaty-puppet-mock

The above puppet provider is just for mocking and easy to understand. It will be a good starter when you want to develope a new puppet by yourself for fullfil your need, for example, connect Wechaty with Wechat Official Account.

Providers

Dependencies

  1. FileBox (npm module file-box) MUST be imported from wechaty-puppet because all the Wechaty Framework needs to check instanceof FileBox, we must be sure all FileBox is the same version.
  2. MemoryCard (npm module memory-card) MUST be imported from wechaty-puppet because all the Wechaty Framework needs to check instanceof MemoryCard, we must be sure all MemoryCard is the same version.

Peer Dependence

Puppet(npm module wechaty-puppet) itself must be a peer Dependencies for all the Puppet Providers, and should only be installed via Wechaty because all Puppet Providers should share the same Puppet Base Class with Wechaty, we must be sure all Puppet is the same version.

Wechaty Puppet Toolsets

1. Using SwitchState

You can get to know the puppet start/stop state from the state property:

  1. puppet.state.on() === 'pending' will be true when the puppet is starting
  2. puppet.state.on() === true will be true when the puppet is started
  3. puppet.state.off() === 'pending' will be true when the puppet is stoping
  4. `puppet.state.off() === true' will be true when the puppet is stopped

Learn more about the puppet.state at https://github.com/huan/state-switch

2. Using Brolog

Using Brolog to output necessary log messages.

2.1 Get log from Brolog

import { log } from 'brolog'

2.2 Log Format

log.verbose('ModuleName', 'methodName() Your Verbose Message Here')
log.silly('ModuleName', 'methodName() Your Silly Message Here')

2.3 Log Level

Brolog has five log levels, it should be used and follow the following rules:

Log Level What does it means Usage in Puppet
log.silly() There's some detail debug information Can be used in everywhere as you like
log.verbose() There's some debug information Should be used at the beginning of every method()
log.info() There's something we need to let user know Should NEVER to be used because Puppet is Library
log.warn() There's a Coverable Error Should not be used unless we have to
log.error() There's a Un-covered Error Should not be used unless we have to

3. Using LRU Cache

Set the max size for wechaty entities in LRU Cache.

Env Name What does it means
WECHATY_PUPPET_LRU_CACHE_SIZE_CONTACT The max cache size for contact, default value 3000
WECHATY_PUPPET_LRU_CACHE_SIZE_FRIENDSHIP The max cache size for friendship, default value 100
WECHATY_PUPPET_LRU_CACHE_SIZE_MESSAGE The max cache size for message, default value 500
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM The max cache size for room, default value 500
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM_INVITATION The max cache size for room invitation, default value 100
WECHATY_PUPPET_LRU_CACHE_SIZE_ROOM_MEMBER The max cache size for room member, default value 30000

Resources

Pure Function

Mixin

History

master v1.0 - Initial Release

v0.49 (Oct 2021)

  1. Add Error interface to EventErrorPayload, and make .data optional
  2. Using Mixin to extend Puppet

v0.43 (Aug 28, 2021)

  1. Support ES Modules

v0.16 (Sep 2019)

Works with the following Puppet Providers:

  1. wechaty-puppet-puppeteer
  2. wechaty-puppet-padchat
  3. wechaty-puppet-mock
  4. wechaty-puppet-wechat4u

v0.0.1 (Jun 2018)

  1. Define the Abstract Puppet Layer for Wechaty
  2. Seperate code from Wechaty

Author

Huan LI (李卓桓) [email protected]

Profile of Huan LI (李卓桓) on StackOverflow

Copyright & License

  • Code & Docs © 2018-now Huan LI <[email protected]>
  • Code released under the Apache-2.0 License
  • Docs released under Creative Commons

puppet's People

Contributors

gcaufy avatar greenkeeper[bot] avatar hcfw007 avatar huan avatar jihuayu avatar leochen-g avatar lucifer1004 avatar padlocal avatar silentqianyi avatar su-chang avatar windmemory avatar zhaoic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

puppet's Issues

clear puppet cache when puppet stop

related issue: wechaty/puppet-service#31

  public async stop (): Promise<void> {
    this.removeListener('heartbeat', this.feedDog)
    this.watchdog.removeListener('reset', this.dogReset)
    this.removeListener('reset', this.throttleReset)

    this.watchdog.sleep()

    /**
     * FIXME: Huan(202008) clear cache when stop
     *  keep the cache as a temp workaround since wechaty-puppet-hostie has reconnect issue
     *  with un-cleared cache in wechaty-puppet will make the reconnect recoverable
     *
     * Related issue: https://github.com/wechaty/wechaty-puppet-hostie/issues/31
     */
    // this.cacheContactPayload.clear()
    // this.cacheFriendshipPayload.clear()
    // this.cacheMessagePayload.clear()
    // this.cacheRoomPayload.clear()
    // this.cacheRoomInvitationPayload.clear()
    // this.cacheRoomMemberPayload.clear()
  }

[RFC] Shall we dirty roomMembers when dirty room

I noticed that, when use room.sync() in wechaty to refresh room information, the room member information won't get refreshed.

I noticed in the code:

  public async roomPayloadDirty (roomId: string): Promise<void> {
    log.verbose('Puppet', 'roomPayloadDirty(%s)', roomId)
    this.cacheRoomPayload.del(roomId)
  }

It won't clean up the roomMemberPayload, and the method to clean up room member roomMemberPayloadDirty is protected, and it only gets called when the room information is invalid and we want to clean up the room information. So in my opinion, if the room member information get updated, we don't have a way to update it, except restart wechaty.

Do you think it makes sense to clean up the room member information when we do dirty on the room?

Please let me know your thoughts. And will submit a PR later.

Type incompatible for VERSION property

Recently I noticed that the typescript is complaining about the type incompatible for VERSION property. So I think it would be better to add a type to the VERSION property as a string.

Class static side 'typeof PuppetPadchat' incorrectly extends base class static side 'typeof Puppet'.
    Types of property 'VERSION' are incompatible.
        Type 'string' is not assignable to type '"0.0.0"'.

[RFC] We need a better error system

Is your feature request related to a problem? Please describe.
Recently we observed more and more limitation pushed out by WeChat. In some cases, connection to WeChat server got kicked out. It would be better that we emit these cases as errors so developers could handle them properly.

Currently all the error messages emitted is an Error instance, only one message on it, so it is really hard to set a solid error type for the error. So here is this issue, I would like to bring this issue up and talk about the design for WechatyError or something else :P

Describe the solution you'd like
We could have a parent error type as WechatyError, then PuppetError extends the parent, then each puppet has its own error class.

How about this design:

class WechatyError extends Error {
  public readonly type: WechatyErrorType
  public readonly message: string
  constructor (
    type: WechatyErrorType,
    message: string,
  ) {
    super()
    this.type = type
    this.message = message
  }
}

Or we could make this parent error as an abstract class, then all the other errors extends this one.

Describe alternatives you've considered
Haven't considered any alternatives...

Additional context
I've implemented one temporary solution in wechaty-puppet-padpro, but that's just a test, you could check it here:
wechaty/wechaty-puppet-padpro@47d8de1

Here is a small piece of code that how I use it:

bot
.on('error', error => {
  const errorMessage = error.message
  const subMessages = errorMessage.split(' ')
  if (subMessages[0] === 'PADPRO_ERROR' && subMessages[1] === 'LOGIN') {
    console.log(`-----------------------------------------------------
    AutoLogin Error:
    type: ${subMessages[2]}
    message: ${subMessages[3]}
    ------------------------------------------------------`)
  }
})
.start()

To be honest, this is ugly, but just treat this as 【抛砖引玉】(don't know how to translate this :P)

Let's make wechaty better and better.

[enhancement]

Support Moment related methods

Methods List Supported by WxWork

  abstract momentSignature (signature?: string)                              : Promise<boolean | string>
  abstract momentCoverage (image: FileBox)                                   : Promise<boolean>
  abstract postTextMoment (content: string, visibleList?: string[])          : Promise<string>
  abstract postLinkMoment (urlLinkPayload: UrlLinkPayload, content?: string) : Promise<string>
  abstract postImageMoment (images: FileBox[], content?: string)             : Promise<string>
  abstract momentPayload (id: string)                                        : Promise<MomentPayload>

Other Methods Should be Supported

  abstract momentList (option?: MomentListOption)                            : Promise<string[]>
  abstract revokeMoment (id: string)                                         : Promise<boolean>
  abstract likeMoment (id: string)                                           : Promise<boolean>
  abstract revokeLikeMoment (id: string)                                     : Promise<boolean>
  abstract commentMoment (id: string, comment: string, commentId?: string)   : Promise<string>
  abstract revokeCommentMoment (commentId: string)                           : Promise<boolean>

Data Structure

export interface MomentPayload {
  authorId: string,
  content?: string,
  urlLink?: UrlLinkPayload,
  images?: FileBox[],
  id: string,
  createTime: number,
  updateTime: number,
}

export interface MomentListOption {
  authorId?: string,
  momentId?: string,
  page?: number,
}

Change `messageForward` to be an abstract method

See: wechaty/puppet-service#135

To-do list

  • update wechaty-puppet-service
  • update wechaty-puppet-wechat
  • update wechaty-puppet-wechat4u
  • update wechaty-puppet-lark
  • update wechaty-puppet-padlocal
  • update wxwork
  • update donut

Source code removal

The following code will be removed and should be copy/pasted to the above puppets as their implementation.

public async messageForward (
  conversationId : string,
  messageId      : string,
): Promise<void | string> {
  log.verbose('Puppet', 'messageForward(%s, %s)', JSON.stringify(conversationId), messageId)

  const payload = await this.messagePayload(messageId)

  let newMsgId: void | string

  switch (payload.type) {

    case MessageType.Attachment:     // Attach(6),
    case MessageType.Audio:          // Audio(1), Voice(34)
    case MessageType.Image:          // Img(2), Image(3)
    case MessageType.Video:          // Video(4), Video(43)
      newMsgId = await this.messageSendFile(
        conversationId,
        await this.messageFile(messageId),
      )
      break

    case MessageType.Text:           // Text(1)
      if (payload.text) {
        newMsgId = await this.messageSendText(
          conversationId,
          payload.text,
        )
      } else {
        log.warn('Puppet', 'messageForward() payload.text is undefined.')
      }
      break

    case MessageType.MiniProgram:    // MiniProgram(33)
      newMsgId = await this.messageSendMiniProgram(
        conversationId,
        await this.messageMiniProgram(messageId)
      )
      break

    case MessageType.Url:            // Url(5)
      await this.messageSendUrl(
        conversationId,
        await this.messageUrl(messageId)
      )
      break

    case MessageType.Contact:        // ShareCard(42)
      newMsgId = await this.messageSendContact(
        conversationId,
        await this.messageContact(messageId)
      )
      break

    case MessageType.ChatHistory:    // ChatHistory(19)
    case MessageType.Location:       // Location(48)
    case MessageType.Emoticon:       // Sticker: Emoticon(15), Emoticon(47)
    case MessageType.Transfer:
    case MessageType.RedEnvelope:
    case MessageType.Recalled:       // Recalled(10002)
      throwUnsupportedError()
      break

    case MessageType.Unknown:
    default:
      throw new Error('Unsupported forward message type:' + MessageType[payload.type])
  }

  if (newMsgId) {
    return newMsgId
  }
}

Discussion: do we need a new event when sync contacts done

I would like to discuss whether we need a new event, emitted when we sync all the contacts after login.

I encountered this problem when I was trying to write the integration tests. I want to do my tests after the contacts are all loaded, but found it is pretty hard to get to that point. I can write tests in login event, but during that time, the contacts are not loaded.

Reason I think we should have this event:
Give the devs an entry point when the contacts are all fully loaded, otherwise, if the developer try to find a contact, but the contact is not fully loaded, and the contactList is from our cache, so the returned contacts collection to be searched is not in ready state. Then the searchContact will fail.

@zixia

Need bump wechaty-puppet version

This commit has not been published success. This commit's version is 0.41.5, but 0.41.6 has already been merged and published.

So we need to bump wechaty-puppet version.

Add params for mini-program to make the surface plot better.

The MiniProgramPayload missed one important key named thumbkey.

The old MiniProgramPayload:

export interface MiniProgramPayload {
    appid?          : string,    // optional, appid, get from wechat (mp.weixin.qq.com)
    description?    : string,    // optional, mini program title
    pagepath?       : string,    // optional, mini program page path
    thumbnailurl?   : string,    // optional, default picture, convert to thumbnail
    title?          : string,    // optional, mini program title
    username?       : string,    // original ID, get from wechat (mp.weixin.qq.com)
}

When we use this object to send MiniProgram, and then the surface plot is not good, so we need more attribute to improve it.

thumbkey? : string,    // original, thumbnailurl and thumbkey will make the headphoto of mini-program better

The thumbkey will be needed, and use it with thumbnailurl will make MiniProgram looks better.

The demo data (authorized by the owner):

const miniProgramPayload = new MiniProgram ({
  appid: 'gh_4c1db055326c',
  title: '泰国免税店免费预约',
  pagepath: 'pages/index/index.html',
  description: '提供曼谷王权免税店、芭提雅免税店、普吉岛王权免税店和普吉岛GMS免税店的免费接送、免费送机、免费餐券和免费自助餐',
  thumbnailurl: '305d0201000456305402010002045a87e2ec02033d0af802045630feb602045dc8d38c042f6175706170706d73675f373233393165353935383839643166655f313537333434323434343130375f3232303331360204010400030201000400',
  thumbkey: 'd73ae9173a9c3a994877ee463c0dea3c',
});

If only use thumbnailurl like http://xxxx.image.jpg, it looks not so good for mini-program.

This method only supported by wechaty-puppet-macpro, and we can get the data of MiniProgram from message when we received a MiniProgram message.

plz add Music support in MessageType.ts

https://github.com/wechaty/wechaty-puppet/blob/17e2efcff8ba70e565d8369d3baea8e573780b51/src/schemas/message.ts#L18

plz add Music support.

image

<?xml version="1.0"?>
<msg>
	<appmsg appid="" sdkver="0">
		<title>【620】旷野吗哪</title>
		<des>点击▶️收听 公众号:云彩助手 每日更新</des>
		<action />
		<type>3</type>
		<showtype>0</showtype>
		<content />
		<url>http://lywx2018.com/ly/audio/2020/mw/mw200817.mp3</url>
		<lowurl>http://lywx2018.com/ly/audio/2020/mw/mw200817.mp3</lowurl>
		<dataurl>http://lywx2018.com/ly/audio/2020/mw/mw200817.mp3</dataurl>
		<lowdataurl />
		<appattach>
			<totallen>0</totallen>
			<attachid />
			<emoticonmd5></emoticonmd5>
			<fileext />
		</appattach>
		<extinfo />
		<sourceusername />
		<sourcedisplayname />
	</appmsg>
	<fromusername><![CDATA[gh_c2138e687da3]]></fromusername>
	<appinfo>
		<version>0</version>
		<appname><![CDATA[云彩助手]]></appname>
		<isforceupdate>1</isforceupdate>
	</appinfo>
</msg>

@see wechaty/wechaty-puppet-padplus#243

Make the stable wechaty-puppet-padplus have a stable wechaty-puppet dependency.

if npx --package @chatie/semver semver-is-prod $VERSION; then
  NPM_TAG=latest
else
  NPM_TAG=next
fi

npm run dist
npm run pack

TMPDIR="/tmp/npm-pack-testing.$$"
mkdir "$TMPDIR"
mv *-*.*.*.tgz "$TMPDIR"
cp tests/fixtures/smoke-testing.ts "$TMPDIR"

cd $TMPDIR
npm init -y
npm install --production \
  ...
  "wechaty-puppet@$NPM_TAG" \
  ...

In the travis CI, it will install wechaty-puppet@latest, but some next features are wanted in the new version of wechaty-puppet-padplus.

So we need to publish a new latest version of wechaty-puppet, and then it will pass the CI pack.

Question about MessagePayload

Definition

type MessagePayload = (MessagePayloadBase & MessagePayloadRoom) | (MessagePayloadBase & MessagePayloadTo)
const payload = messageRawPayloadParser(): MessagePayload

If we want to get mentionIdList of payload, we have to claim that:

(payload as (MessagePayloadBase & MessagePayloadRoom)).mentionIdList

But MessagePayloadBase and MessagePayloadRoom are not export in wechaty-puppet, so any good idea about this issue?

Add new type to Puppet: `ScanStatus`

The scan event always comes with a status code, which will indicate whether the QR Code had been scanned, or had been confirmed for logging in.

The current type of status code is number, which has the following problem:

  1. it's not typed. the different puppet implementation might use the different magic number to stands for different meanings.
  2. it's meaning is not straightforward.

So we decide to add a ScanStatus enum type to puppet system:

/**
 * The event `scan` status number.
 */
export enum ScanStatus {
  Waiting   = 0,
  Scanned   = 1,
  Confirmed = 2,
}

It will contain three status: Waiting, Scanned, and Confirmed.

I will implement it first, and keep this issue open for discussion for future improvements.

@windmemory @lijiarui @linyimin-bupt Please feel free to let me know if you have any comments.

npm run dist failed

Here is the shell output:

src/puppet.spec.ts:370:49 - error TS2345: Argument of type '(roomId: string) => RoomPayload' is not assignable to parameter of type '(roomId: string) => Promise<RoomPayload>'.
  Type 'RoomPayload' is not assignable to type 'Promise<RoomPayload>'.
    Property 'then' is missing in type 'RoomPayload'.

370   sandbox.stub(puppet, 'roomPayload').callsFake(roomId => {

Seems like this is introduced by new typescript version.

Refactoring the `'dirty'` event, `dirtyPayload()`, and `XXXPayloadDirty()` methods logic & spec

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

https://martinfowler.com/bliki/TwoHardThings.html

Working PRs

The New Design

In this new design, we will use:

  1. 'dirty' event (added via wechaty/grpc#79)
  2. dirtyPayload() method (added via #114)
  3. XXXPayloadDirty() method (removed via #114)

With the logic:

  1. 'dirty' event will be emitted by the deepest puppet by calling the dirtyPayload() method:

    PuppetServiceClient(PuppetServiceServer(PuppetAbstract))).dirtyPayload() -> PuppetAbstract.emit('dirty', payload)

  2. Every Puppet listen to 'dirty' event with its onDirty() method, and invalid(remove) the cache of the payload with the specific id and type (register onDirty() callback to the dirty event will be done automatically by the PuppetAbstract)
  3. XXXPayloadDirty() should call dirtyPayload() first, then wait the 'dirty' event. After receiving the 'dirty' event, it can return because the payload should be dirtied by the listener (which should be registered by the Puppet itself)
    1. a timer should be added in case of the 'dirty' event was lost, and throw an Error. (5 seconds in the current code)

New Puppet API: onDirty()

  1. onDirty() will be addListener to the dirty event by the PuppetAbstract
  2. child puppets need to override it to clean their own payload cache/store (and do not forget to call super.onDirty()!)

Dirty a Payload Locally (in Puppet Provider)

How to dirty a payload (for puppet provider, locally):

  1. puppet.XXXPayloadDirty(id)
    1. future = await new Promise(resolve => puppet.once('dirty', resolve))
      1. check the dirty id/type match before resolve
      2. add a timer to reject the Promise if timeout
  2. puppet.dirtyPayload('XXX', id)
    1. setImmediate(() => puppet.emit('dirty', { type: 'XXX', id })) <- add this task (emit) to the end of event loop queue
  3. puppet.onDirty()
    2. clean the cache
  4. puppet.XXXPayloadDirty(id) <- future resolved
    1. await new Promise(setImmediate) <- wait current event loop task queue to be all executed

call 1, emit 2, listen 3, resolve 4

Dirty a Payload Remotely (with Puppet Service)

  1. 🖥️ Puppet Service
    1. puppetService.XXXPayloadDirty(id)
      1. future = await new Promise(resolve => puppet.once('dirty', resolve))
        1. check the dirty id/type match before resolve
        2. add a timer to reject the Promise if timeout
    2. puppetService.dirtyPayload('XXX', id)
    3. gRPC Service DirtyPayload
  2. ☁️ Puppet Server
    1. puppetServer.dirtyPayload('XXX', id)
    2. puppetServer.emit('dirty', { type: 'XXX', id })
    3. puppetServer.onDirty() listener: remove payload cache
    4. gRPC Service Event (Stream)
  3. 🖥️ Puppet Service
    1. puppetClient.emit('dirty', { type: 'XXX', id })
    2. puppetClient.onDirty() listener: remove payload cache
    3. puppetClient.XXXPayloadDirty(id) future resolved

Steps Mapping

Locally Remote Client Remote Server
1 1. i 2.i
2 1. ii 2.ii
3 3. ii 2.iii
4 3. iii 2.iv

Related issues

Breaking Change

This change will not affect the end-users.

However, the puppet provider developer needs to follow this new logic when they are using the new version of Puppet Abstract Class.

The Puppet Service will be affected too, so we recommend paying attention to the Wechaty versions to be matching on both the server and the client.

CC @wechaty/puppet @wechaty/grpc

ESM support will be required after Wechaty v0.69+

Currently, the Wechaty ecosystem is moving from CommonJS to ES Modules.

The Wechaty will be Dual-ESM-CJS-Module supported, and the default mode will be under ESM.

In order to load Wechaty Puppet under ESM mode by setting the environment variable WECHATY_PUPPT, or by setting the options.puppet = 'wechaty-puppet-xxx', the puppet provider NPM module must support ESM.

If the puppet does not support ESM, then the Wechaty will run into the below issue: TypeError: MyPuppet is not a constructor

Lock the ENUM value in TS

Currently, we have ENUM without hardcore the number value of it.

See: https://github.com/wechaty/wechaty-puppet/tree/master/src/schemas

In TS this will not be a problem as long as we are using the same version for both server and client.

However, in our multi language Wechaty ecosystem, other languages have to use the hard coded value.

So we need to lock all ENUM values in TS, to prevent the future u expected breaking changes.

Update git-scripts

I used yarn command to install the dependencies, but it throw a error.

error C:\Users\jihuayu\Documents\.sc\wechaty-puppet\node_modules\@chatie\git-scripts: Command failed.
Exit code: 127
Command: bash scripts/install.sh
Arguments:

I think you should update @chatie/git-scripts to make it can run in Windows.

Thanks

Env

OS : Windows10
Node: v14.4.0
yarn: 1.22.4

Enforce all `constructor(options)` function to be consistent for Puppet Implementations

https://github.com/Chatie/wechaty/blob/63713ba7e73d17e1a99b59cbd7e4f6feeb8e4747/src/wechaty.ts#L527

With the above code, we will meet the following error:

[ts] Cannot use 'new' with an expression whose type lacks a call or construct signature.

When we have different puppet with different constructor() args.

For example: PuppetA allow constructor() but PuppetB requires constructor(options)

SOLUTION: we enforce all the PuppetImplenmentation to have options default value.

Building failed, caused by accessing private property options

Here is the error log:

no-unused-variable is deprecated. Since TypeScript 2.9. Please use the built-in compiler checks instead.
src/puppet.ts(231,19): error TS2341: Property 'options' is private and only accessible within class 'MemoryCard'.
src/puppet.ts(231,42): error TS2341: Property 'options' is private and only accessible within class 'MemoryCard'.
src/puppet.ts(252,21): error TS2341: Property 'options' is private and only accessible within class 'MemoryCard'.
src/puppet.ts(252,44): error TS2341: Property 'options' is private and only accessible within class 'MemoryCard'.
src/puppet.ts(253,87): error TS2341: Property 'options' is private and only accessible within class 'MemoryCard'.

My change broke this, I would submit a fix for this issue.

Do filters inside map function

Recently we observed memory leak issue when using the padchat. Then I found this piece of code, seems like we are doing find after we load all the contact information. Here is the code:

https://github.com/Chatie/wechaty-puppet/blob/e590d7779205150586c60909675a4bc5bfaddf3a/src/puppet.ts#L491-L522

I think it would be better that we do the filters inside the map function, immediately after we get the payload, we do the filter function on the payload, if the payload is the one we are trying to find, then we return it, otherwise we will return null or undefined. Then we do a filter outside the map function.

What do you think @zixia

Mixin: Property 'messageRawPayload' of exported class expression may not be private or protected.ts(4094)

The Problem

Mixin does not allow the use of protected and private with property/method

The Solution

  1. Change them with public and a _ prefix?
  2. Provide a PuppetInterface by defining a ProtectedMethods type to calculate from Omit<Puppet, ProtectedMethods>

Links

`reset()` method and `'reset'` event breaking change

In previous (v0.49-) version, the puppet reset action was triggered by the 'reset' event.

Before (v0.49-)

In order to reset the puppet, what we need to do is:

puppet.emit('reset', 'this will reset the puppet')

the puppet.reset() is protected (not public) and will not be called by the user.

After (v0.51+)

In order to reset the puppet, it's straightforward to call the reset() method (which has been changed to public)

puppet.reset('this will reset the puppet')

Promise.all with concurrency limitation implementation

Related to wechaty/puppet-service#31

Currently the code logic for roomSearch in wechaty-puppet is not using batch operation, like below:

https://github.com/wechaty/wechaty-puppet/blob/804b31b970e931547238d59de43847178a2869cd/src/puppet.ts#L1174-L1212

When the query is passed to the function, it will do a Promise.all() of all rooms, which might cause the problem that we've been fighting for a long time, the grpc issue. So I think we need to use batch operation for this method to avoid the grpc problem.

Add Typing support for RxJS `fromEvent`

In RxJS, we can use fromEvent to convert an EventEmitter to an Observable:

const message$ = fromEvent(wechaty.puppet, 'message')

However, fromEvent does not use on() for type infer, it uses addEventListener and removeEventListener.

See: https://github.com/ReactiveX/rxjs/blob/49304ffef8d7a0663c57fe8e673359a602e9d3e1/src/internal/observable/fromEvent.ts#L27-L30

So we need to add addEventListener and removeEventListener for support it.

Update 1

According to https://rxjs-dev.firebaseapp.com/api/index/function/fromEvent

fromEvent accepts as a first argument event target, which is an object with methods for registering event handler functions.

It turns out that it's not suitable for supporting it on Wechaty because wechaty listeners have variable numbers of the arguments.

Thinking about to support it at the puppet level.

Update 2

Move this issue from wechaty to wechaty-puppet

Update 3

Unit test:

The signature '(target: NodeStyleEventEmitter | ArrayLike<NodeStyleEventEmitter>, eventName: string): Observable<EventScanPayload>' of 'fromEvent' is deprecated.ts(6387)
fromEvent.d.ts(36, 5): The declaration was marked as deprecated here.
(alias) fromEvent<EventScanPayload>(target: NodeStyleEventEmitter | ArrayLike<NodeStyleEventEmitter>, eventName: string): Observable<...> (+11 overloads)
import fromEvent
@deprecated  Do not specify explicit type parameters. Signatures with type parameters that cannot be inferred will be removed in v8.

https://github.com/wechaty/wechaty-puppet/blob/d7f069e108d79236c65a1cd956800716d2df38fc/tests/from-event-type.spec.ts#L43-L44

Update 4 (Nov 114, 2021)

A typing of FromEvent for typed-emitter has been published and it works as expected:

Room has no id bread Room.findAll

00:26:34 VERB Room findAll() rejected: no id
Error: no id
    at PuppetPadchat.<anonymous> (/wechaty/node_modules/wechaty-puppet/src/puppet.ts:850:13)
    at Generator.next (<anonymous>)
    at /wechaty/node_modules/wechaty-puppet/dist/src/puppet.js:25:71
    at new Promise (<anonymous>)
    at __awaiter (/wechaty/node_modules/wechaty-puppet/dist/src/puppet.js:21:12)
    at PuppetPadchat.roomPayload (/wechaty/node_modules/wechaty-puppet/dist/src/puppet.js:510:16)
    at AnotherOriginalClass.<anonymous> (/wechaty/src/user/room.ts:293:23)
    at Generator.next (<anonymous>)
    at /wechaty/dist/src/user/room.js:7:71
    at new Promise (<anonymous>)
    at __awaiter (/wechaty/dist/src/user/room.js:3:12)
    at AnotherOriginalClass.ready (/wechaty/dist/src/user/room.js:241:16)
    at Promise.all.roomList.map.room (/wechaty/src/user/room.ts:128:27)
    at Array.map (<anonymous>)
    at Function.<anonymous> (/wechaty/src/user/room.ts:125:18)
    at Generator.next (<anonymous>)
    at fulfilled (/wechaty/dist/src/user/room.js:4:58)
    at process._tickCallback (internal/process/next_tick.js:68:7)

Exception is thrown out when trying to get all the room payload. I think issue should be here:
https://github.com/Chatie/wechaty-puppet/blob/5b24558cc24fe1fc30178820467a7ffc41b047ea/src/puppet.ts#L829-L833

The id might be undefined or null, so in roomPayload function the error thrown out:
https://github.com/Chatie/wechaty-puppet/blob/5b24558cc24fe1fc30178820467a7ffc41b047ea/src/puppet.ts#L935-L942

Comparing to contactSearch
https://github.com/Chatie/wechaty-puppet/blob/5b24558cc24fe1fc30178820467a7ffc41b047ea/src/puppet.ts#L491-L507

I think we should do similar things for room, when error happens, catch it and log it, but not throw exception that breaks the whole process.

Does this make sense to you @zixia ?

mixin-ization Puppet Abstract Class

What is Mixin

Along with traditional OO hierarchies, another popular way of building up classes from reusable components is to build them by combining simpler partial classes. You may be familiar with the idea of mixins or traits for languages like Scala, and the pattern has also reached some popularity in the JavaScript community.
https://www.typescriptlang.org/docs/handbook/mixins.html

Mixin is a great decouple and design pattern to split the puppet class logic into different files.

Wechaty Puppet Mixins

  1. We will have a PuppetSkelton as the base abstract class for all Mixins, it
    1. extends the PuppetEventEmitter for adding the listener typings
    2. has the least components to support other mixins
  2. All functionalities will be added by mixin, for example:
    1. CacheMixin for puppet.cache related codes
    2. WatchdogMixin for puppet.watchdog related codes
    3. MemoryMixin for puppet.memory related codes
    4. MessageMixin, ContactMixin, RoomMixin, etc to add related puppet abstract APIs
  3. All mixins need to implement a start() and stop() and call super.start() and super.stop() inside them, to make sure the start/stop chains can work as expected.

Related issues

Add new message type VideoPost

Good News

wechaty-puppet-wxwork support forward Channels message

Channels introduction: WeChat Channels: a comprehensive guide

So we should do that:

  • add message type: MessageType.Channels
  • add schema ChannelsPayload for get the message detail
export interface ChannelsPayload {
  authorAvatar: string, // channel author avatar
  authorNickname: string, // channel author nickname
  coverageUrl: string, // channels coverage url
  title: string, // channels tilte
  videoUrl: string, // channel video url
}

Wechaty Puppet Implementation NPM checklist

  • wechaty-puppet must not a dependency. It should be put in devDependencies and peerDependencies
  • wechaty must not a dependency. It should be put in devDependencies and peerDependencies
  • must exist examples/ding-dong-bot.ts to implement the ding/dong logic, use puppet api only.

BREAKING CHANGE: Refactoring the Tag API: use id for a specific tag

Currently, we are using the tag string as the id of the tag itself.

However, it seems that in the Wechat system internal, every tag has an id, and we can rename a tag by change the string attached to that id so that all contacts/favorites with that id will be affected.

To be refactored.

Do Not Block the Event Loop

Currently, the implementation of Puppet has bugs that will block the event loop, because the array is too big and we have a sync loop to deal with lots of the data.

Should split the Array to Chunks with a max size like 64 or 128.

See Also

Why WechatAppMessageType and WechatMessageType are private?

I would like to use the type to map the message, but I found they are private and not being exported. Should I create my own copy of types in wechaty-puppet-padchat or share this one? I think the types should be shared across all platforms, so I think it would be better to make it public and exported it from wechaty-puppet

The ding/dong way: how to create a communication channel between Wechaty and the deepest puppet provider

Background

The Wechaty ecosystem is involved very fast in the past months, we have lots of new features and bug fixes.

It is well designed and decoupled, that's why we can support multi-language and more and more puppet protocols without any difficulty.

However, sometimes we want to call the puppet methods freely and without any limitations, for example:

  1. We need to call some puppet methods for debugging
  2. We need to provide a new puppet feature very quickly (without modifying the puppet base class and the GRPC service)
  3. We want to test some puppet specified methods
  4. etc.

The Problem

If you want to call a puppet provider from Wechaty, what you can do is:

(wechaty.puppet as any).XXXMethod()

Which XXXMethod() is the method you want to call from your puppet. By using the wechaty.puppet as any trick, we can call any method on the puppet, even the private methods.

However, this is not deep enough when we are using the Hostie Puppet.

The hostie puppet is using the GRPC for providing a deeper decouple between Wechaty and puppets, which means that the Wechaty.puppet can not reach the real puppet behind the GRPC service.

The Solution

This solution came out when I have Chongqing hot pot with @lijiarui and @qhduan.

We can use our ding() method and the dong event to build an RPC channel between the Wechaty and the deepest puppet provider.

The design of the ding/dong mechanism is:

  1. wechaty.ding(data: string) is a method that can be called with a string parameter, on the Wechaty
  2. the ding() will be called on the puppet that provides the service to this Wechaty, the deepest one behind the GRPC service
  3. the puppet behind the GRPC service then will be able to emit a dong event, with a data payload, to pass a string back
  4. the wechaty will receive a dong event, with the same data payload from the puppet.

This lets us be able to build a channel to send/receive string data between the wechaty and puppet.

Then we can use a JSON-GRPC to do any remote grpc call based on this channel.

Conclusion

After I got this idea, I think it will be the best way to add the maximum flexibility to our Wechaty system for doing anything between the Wechaty and the puppet.

Will consider building an example with JSON-RPC to demonstrate this idea.

增加type字段来快捷识别群的类型

  • 个人微信

    • 内部群(由个人微信组成的群)

    • 外部群(由个人微信和企业微信组成的混合群)

  • 企业微信

    • 内部群(由当前企业下企业员工组成的群)

    • 外部群(由外部联系人和企业员工组成的混合群)

How to mark the room which the bot are not in.

When the bot has been removed from one room, but the RoomPayload will still store in wechaty-puppet-padplus cache, and it will make mistake when we find all room next time.

So I think we need to mark these rooms.

So dose ContactPayload.

Needs to stop watchdog in puppet implementation

Is there a way to stop the watchdog in puppet implementation? I want to stop the watchdog in wechaty-puppet-hostie so I can deal with reconnection logic when the server is temporary unavailable.

BREAKING CHANGE v0.21: Plan to remove some exported `Interface`s

Today I found there have six definitions that are not used by the wechaty-puppet itself:

  1. YOU
  2. PuppetQRCodeScanEvent
  3. PuppetRoomInviteEvent
  4. PuppetRoomJoinEvent
  5. PuppetRoomLeaveEvent
  6. PuppetRoomTopicEvent

The code:

https://github.com/wechaty/wechaty-puppet/blob/862137f35f94fa84a29957dc8c674ab103174360/src/schemas/puppet.ts#L3-L50

I'm planning to remove them.

There are serval puppet providers (wechaty-puppet-padplus for example) using those import, after the removal, those consumers need to copy/paste those Interfaces to local, instead of import them from the wechaty-puppet.

Please let me know if there's any problems with the removal.

Thank you very much.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.