wechaty.ts 12.0 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
 * @see The <a href="https://github.com/lijiarui/wechaty-getting-started">Wechaty Starter Project</a>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
64
 */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
65
export class Wechaty extends EventEmitter implements Sayable {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
66 67
  /**
   * singleton _instance
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
68
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
69
   */
70
  private static _instance: Wechaty
71

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

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

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

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

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

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

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
134 135
  /**
   * Return version of Wechaty
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
136
   * @param [forceNpm=false] {boolean} if set to true, will only return the version in package.json.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
137 138 139 140 141 142 143 144 145
   *                            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 (李卓桓) 已提交
146
    // TODO: use  git rev-parse HEAD  ?
147
    const dotGitPath  = path.join(__dirname, '..', '.git') // only for ts-node, not for dist
148 149
    const gitLogCmd   = 'git'
    const gitLogArgs  = ['log', '--oneline', '-1']
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
150

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
289 290 291 292 293 294 295 296 297 298 299 300 301
    // 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)
    // })
302

303
    return this
304 305
  }

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

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

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

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

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

    eventList.map(e => {
343 344
      // 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) => ...)
345
      puppet.on(e, (...args: any[]) => {
346 347
        // this.emit(e, data)
        this.emit.apply(this, [e, ...args])
348
      })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
349
    })
350

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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