wechaty.ts 11.9 KB
Newer Older
1 2
import { EventEmitter } from 'events'
import * as fs          from 'fs'
3
import * as path        from 'path'
4

5 6
import {
    Config
7
  , HeadName
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
8
  , PuppetName
9
  , Sayable
10
  , log
11
}                     from './config'
12

13 14 15 16 17 18 19 20
import { Contact }        from './contact'
import { FriendRequest }  from './friend-request'
import { Message }        from './message'
import { Puppet }         from './puppet'
import { PuppetWeb }      from './puppet-web/'
import { Room }           from './room'
import { StateMonitor }   from './state-monitor'
import { UtilLib }        from './util-lib'
21

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
22
export type PuppetSetting = {
23
  head?:    HeadName
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
24
  puppet?:  PuppetName
25
  profile?: string
26
}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
27

28 29 30 31 32 33 34 35 36 37 38
export type WechatyEventName = 'error'
                              | 'friend'
                              | 'heartbeat'
                              | 'login'
                              | 'logout'
                              | 'message'
                              | 'room-join'
                              | 'room-leave'
                              | 'room-topic'
                              | 'scan'
                              | 'EVENT_PARAM_ERROR'
39

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
40 41 42 43 44 45 46 47 48 49
/**
 *
 * Wechaty: Wechat for ChatBots.
 * Connect ChatBots
 *
 * Class Wechaty
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
50 51 52 53 54 55 56 57 58 59 60 61 62
 *
 * **Example**
 *
 * ```ts
 * // The World's Shortest ChatBot Code: 6 lines of JavaScript
 * const { Wechaty } = require('wechaty')
 *
 * Wechaty.instance() // Singleton
 * .on('scan', (url, code) => console.log(`Scan QR Code to login: ${code}\n${url}`))
 * .on('login',       user => console.log(`User ${user} logined`))
 * .on('message',  message => console.log(`Message: ${message}`))
 * .init()
 * ```
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
63
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64
export class Wechaty extends EventEmitter implements Sayable {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
65 66
  /**
   * singleton _instance
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
67
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
68
   */
69
  private static _instance: Wechaty
70

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
71 72
  /**
   * the puppet
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
73
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
74
   */
75
  public puppet: Puppet | null
76

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
77 78
  /**
   * the state
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
79
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
80
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
81
  private state = new StateMonitor<'standby', 'ready'>('Wechaty', 'standby')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
82 83
  /**
   * the npmVersion
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
84
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
85
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
86
  private npmVersion: string = require('../package.json').version
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
87 88
  /**
   * the uuid
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
89
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
90
   */
91
  public uuid:        string
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
92

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
93
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
94
   * get the singleton instance of Wechaty
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
95
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
96
  public static instance(setting?: PuppetSetting) {
97
    if (setting && this._instance) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
98
      throw new Error('there has already a instance. no params will be allowed any more')
99 100 101 102 103 104 105
    }
    if (!this._instance) {
      this._instance = new Wechaty(setting)
    }
    return this._instance
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
106
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
107
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
108
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
109
  private constructor(private setting: PuppetSetting = {}) {
110
    super()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
111 112
    log.verbose('Wechaty', 'contructor()')

113
    setting.head    = setting.head    || Config.head
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
114
    setting.puppet  = setting.puppet  || Config.puppet
115 116
    setting.profile = setting.profile || Config.profile

117
    // setting.port    = setting.port    || Config.port
118

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
119 120
    if (setting.profile) {
      setting.profile  = /\.wechaty\.json$/i.test(setting.profile)
121 122
                        ? setting.profile
                        : setting.profile + '.wechaty.json'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
123
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
124

125
    this.uuid = UtilLib.guid()
126 127
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
128
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
129
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
130
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
131
  public toString() { return 'Class Wechaty(' + this.setting.puppet + ')'}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
132

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
133 134
  /**
   * Return version of Wechaty
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
135
   * @param [forceNpm=false] {boolean} if set to true, will only return the version in package.json.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
136 137 138 139 140 141 142 143 144
   *                            otherwise will return git commit hash if .git exists.
   * @returns {string} version number
   * @example
   *  console.log(Wechaty.instance().version())
   *  // #git[af39df]
   *  console.log(Wechaty.instance().version(true))
   *  // 0.7.9
   */
  public version(forceNpm = false): string {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
145
    // TODO: use  git rev-parse HEAD  ?
146
    const dotGitPath  = path.join(__dirname, '..', '.git') // only for ts-node, not for dist
147 148
    const gitLogCmd   = 'git'
    const gitLogArgs  = ['log', '--oneline', '-1']
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
149

150 151
    if (!forceNpm) {
      try {
152
        fs.statSync(dotGitPath).isDirectory()
153 154 155

        const ss = require('child_process')
                    .spawnSync(gitLogCmd, gitLogArgs, { cwd:  __dirname })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
156

157 158 159 160 161 162 163
        if (ss.status !== 0) {
          throw new Error(ss.error)
        }

        const revision = ss.stdout
                          .toString()
                          .trim()
164
        return `#git[${revision}]`
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
165

166
      } catch (e) { /* fall safe */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
167 168 169 170
        /**
         *  1. .git not exist
         *  2. git log fail
         */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
171
        log.silly('Wechaty', 'version() form development environment is not availble: %s', e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
172
      }
173 174 175
    }

    return this.npmVersion
176
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
177

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
178 179
  /**
   * @todo document me
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
180
   * @returns {Contact}
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
181
   * @deprecated
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
182
   */
Huan (李卓桓)'s avatar
fix #54  
Huan (李卓桓) 已提交
183
  public user(): Contact {
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
184 185
    log.warn('Wechaty', 'user() DEPRECATED. use self() instead.')

Huan (李卓桓)'s avatar
fix #54  
Huan (李卓桓) 已提交
186 187 188 189 190
    if (!this.puppet || !this.puppet.user) {
      throw new Error('no user')
    }
    return this.puppet.user
  }
191

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
192
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
193
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
194
   */
195
  public async reset(reason?: string): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
196
    log.verbose('Wechaty', 'reset() because %s', reason)
197 198 199
    if (!this.puppet) {
      throw new Error('no puppet')
    }
200 201
    await this.puppet.reset(reason)
    return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
202
  }
203

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
204 205 206
  /**
   * @todo document me
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
207
  public async init(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
208
    log.info('Wechaty', 'v%s initializing...' , this.version())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
209
    log.verbose('Wechaty', 'puppet: %s'       , this.setting.puppet)
210 211
    log.verbose('Wechaty', 'head: %s'         , this.setting.head)
    log.verbose('Wechaty', 'profile: %s'      , this.setting.profile)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
212
    log.verbose('Wechaty', 'uuid: %s'         , this.uuid)
213

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
214
    if (this.state.current() === 'ready') {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
215
      log.error('Wechaty', 'init() already inited. return and do nothing.')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
216
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
217 218
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
219
    this.state.target('ready')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
220 221
    this.state.current('ready', false)

222 223 224
    try {
      await this.initPuppet()
    } catch (e) {
Huan (李卓桓)'s avatar
bug fix  
Huan (李卓桓) 已提交
225
      log.error('Wechaty', 'init() exception: %s', e && e.message)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
226
      throw e
227
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
228 229

    this.state.current('ready')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230
    return
231
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
232

233
  // public on(event: WechatyEventName, listener: Function): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
234
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
235 236 237 238
   * @listens Wechaty#error
   * @param [event='error'] {string}    the `error` event
   * @param listener        {Function}  listener (error) => void callback function
   * @return                {Wechaty}   this for chain
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
239
   */
240
  public on(event: 'error'      , listener: (this: Wechaty, error: Error) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
241 242 243
  /**
   * @todo document me
   */
244
  public on(event: 'friend'     , listener: (this: Wechaty, friend: Contact, request?: FriendRequest) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
245 246 247
  /**
   * @todo document me
   */
248
  public on(event: 'heartbeat'  , listener: (this: Wechaty, data: any) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
249 250 251
  /**
   * @todo document me
   */
252
  public on(event: 'logout'     , listener: (this: Wechaty, user: Contact) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
253 254 255
  /**
   * @todo document me
   */
256
  public on(event: 'login'      , listener: (this: Wechaty, user: Contact) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
257 258 259
  /**
   * @todo document me
   */
260
  public on(event: 'message'    , listener: (this: Wechaty, message: Message) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
261 262 263
  /**
   * @todo document me
   */
264
  public on(event: 'room-join'  , listener: (this: Wechaty, room: Room, inviteeList: Contact[],  inviter: Contact) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
265 266 267
  /**
   * @todo document me
   */
268
  public on(event: 'room-leave' , listener: (this: Wechaty, room: Room, leaverList: Contact[]) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
269 270 271
  /**
   * @todo document me
   */
272
  public on(event: 'room-topic' , listener: (this: Wechaty, room: Room, topic: string, oldTopic: string, changer: Contact) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
273 274 275
  /**
   * @todo document me
   */
276
  public on(event: 'scan'       , listener: (this: Wechaty, url: string, code: number) => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
277 278 279
  /**
   * @todo document me
   */
280
  public on(event: 'EVENT_PARAM_ERROR', listener: () => void): this
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281 282 283
  /**
   * @todo document me
   */
284

285
  public on(event: WechatyEventName, listener: Function): this {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
286
    log.verbose('Wechaty', 'addListener(%s, %s)', event, typeof listener)
287

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
288 289 290 291 292 293 294 295 296 297 298 299 300
    // const thisWithSay: Sayable = {
    //   say: (content: string) => {
    //     return Config.puppetInstance()
    //                   .say(content)
    //   }
    // }

    super.on(event, listener) // `this: Wechaty` is Sayable

    // (...args) => {
    //
    //   return listener.apply(this, args)
    // })
301

302
    return this
303 304
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
305 306
  /**
   * @todo document me
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
307
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
308
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
309
  public async initPuppet(): Promise<Puppet> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
310
    let puppet: Puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
311 312 313 314 315

    if (!this.setting.head) {
      throw new Error('no head')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
316
    switch (this.setting.puppet) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
317
      case 'web':
318
        puppet = new PuppetWeb({
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
319
            head:     this.setting.head
320 321
          , profile:  this.setting.profile
        })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
322
        break
323

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
324
      default:
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
325
        throw new Error('Puppet unsupport(yet?): ' + this.setting.puppet)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
326
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
327

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
328 329 330 331 332 333 334 335 336 337 338 339 340 341
    const eventList: WechatyEventName[] = [
        'error'
      , 'friend'
      , 'heartbeat'
      , 'login'
      , 'logout'
      , 'message'
      , 'room-join'
      , 'room-leave'
      , 'room-topic'
      , 'scan'
    ]

    eventList.map(e => {
342 343
      // https://strongloop.com/strongblog/an-introduction-to-javascript-es6-arrow-functions/
      // We’ve lost () around the argument list when there’s just one argument (rest arguments are an exception, eg (...args) => ...)
344
      puppet.on(e, (...args: any[]) => {
345 346
        // this.emit(e, data)
        this.emit.apply(this, [e, ...args])
347
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
348
    })
349

350
    // set puppet before init, because we need this.puppet if we quit() before init() finish
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
351
    this.puppet = <Puppet>puppet // force to use base class Puppet interface for better encapsolation
352 353 354

    // set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
    Config.puppetInstance(puppet)
355

356
    await puppet.init()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
357
    return puppet
358 359
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
360 361 362
  /**
   * @todo document me
   */
363
  public async quit(): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
364
    log.verbose('Wechaty', 'quit()')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
365
    this.state.current('standby', false)
366

367 368
    if (!this.puppet) {
      log.warn('Wechaty', 'quit() without this.puppet')
369
      return
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
370 371
    }

372
    const puppetBeforeDie = this.puppet
373 374
    this.puppet     = null
    Config.puppetInstance(null)
375

376 377 378 379 380
    await puppetBeforeDie.quit()
                        .catch(e => {
                          log.error('Wechaty', 'quit() exception: %s', e.message)
                          throw e
                        })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
381
    this.state.current('standby')
382
    return
383
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
384

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
385 386 387
  /**
   * @todo document me
   */
388
  public async logout(): Promise<void>  {
389 390 391
    if (!this.puppet) {
      throw new Error('no puppet')
    }
392 393 394 395 396 397
    await this.puppet.logout()
                    .catch(e => {
                      log.error('Wechaty', 'logout() exception: %s', e.message)
                      throw e
                    })
    return
398
  }
399

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
400
  /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
401
   * get current user
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
402
   * @returns {Contact} current logined user
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
403
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
404
  public self(): Contact {
405
    if (!this.puppet) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
406
      throw new Error('Wechaty.self() no puppet')
407
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
408
    return this.puppet.self()
409
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
410

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
411 412 413
  /**
   * @todo document me
   */
414
  public async send(message: Message): Promise<void> {
415 416 417
    if (!this.puppet) {
      throw new Error('no puppet')
    }
418
    await this.puppet.send(message)
419 420 421 422
                      .catch(e => {
                        log.error('Wechaty', 'send() exception: %s', e.message)
                        throw e
                      })
423
    return
424
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
425

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
426 427 428
  /**
   * @todo document me
   */
429
  public async say(content: string): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
430 431
    log.verbose('Wechaty', 'say(%s)', content)

432 433 434
    if (!this.puppet) {
      throw new Error('no puppet')
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
435
    this.puppet.say(content)
436
    return
437 438
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
439 440 441
  /**
   * @todo document me
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
442 443
  public async sleep(millisecond: number): Promise<void> {
    await new Promise(resolve => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
444 445 446 447
      setTimeout(resolve, millisecond)
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
448 449
  /**
   * @todo document me
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
450
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
451
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
452
  public ding() {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
453 454 455 456
    if (!this.puppet) {
      return Promise.reject(new Error('wechaty cant ding coz no puppet'))
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
457
    return this.puppet.ding() // should return 'dong'
458 459 460 461
                      .catch(e => {
                        log.error('Wechaty', 'ding() exception: %s', e.message)
                        throw e
                      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
462
  }
463
}