wechaty.ts 36.9 KB
Newer Older
1
/**
2
 *   Wechaty - https://github.com/chatie/wechaty
3
 *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
4
 *   @copyright 2016-2018 Huan LI <zixia@zixia.net>
5
 *
6 7 8
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
9
 *
10
 *       http://www.apache.org/licenses/LICENSE-2.0
11
 *
12 13 14 15 16
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
L
lijiarui 已提交
17 18
 *
 *  @ignore
19
 */
20 21
import cuid    from 'cuid'
import os      from 'os'
22

23
import {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
24
  // Constructor,
25
  cloneClass,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
26
  // instanceToClass,
27
}                   from 'clone-class'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
28 29 30
import {
  FileBox,
}                   from 'file-box'
31 32 33
import {
  callerResolve,
  hotImport,
34
}                   from 'hot-import'
35 36 37
import {
  MemoryCard,
}                   from 'memory-card'
38 39 40
import {
  StateSwitch,
}                   from 'state-switch'
41

42
import {
43
  CHAT_EVENT_DICT,
44 45 46 47
  Puppet,

  PUPPET_EVENT_DICT,
  PuppetEventName,
48
  PuppetOptions,
49 50
}                       from 'wechaty-puppet'

51 52 53
import {
  Accessory,
}                       from './accessory'
54
import {
55
  config,
56
  isProduction,
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
57
  log,
58
  Raven,
59
  VERSION,
60
}                       from './config'
61

62 63 64 65 66
import {
  AnyFunction,
  Sayable,
}                       from './types'

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
67 68 69
import {
  Io,
}                       from './io'
M
Mukaiu 已提交
70
import {
71 72
  PuppetName,
}                       from './puppet-config'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
73 74 75
import {
  PuppetManager,
}                       from './puppet-manager'
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
76

77
import {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
78
  Contact,
79
  ContactSelf,
80
  Friendship,
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
81 82
  Message,
  Room,
83
  RoomInvitation,
84
}                       from './user/'
85 86

export const WECHATY_EVENT_DICT = {
87
  ...CHAT_EVENT_DICT,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
88
  dong      : 'tbw',
89 90
  error     : 'tbw',
  heartbeat : 'tbw',
91
  ready     : 'All underlined data source are ready for use.',
92 93
  start     : 'tbw',
  stop      : 'tbw',
94 95 96
}

export type WechatyEventName  = keyof typeof WECHATY_EVENT_DICT
97 98

export interface WechatyOptions {
99 100 101 102
  profile?       : null | string,           // Wechaty Name
  puppet?        : PuppetName | Puppet,     // Puppet name or instance
  puppetOptions? : PuppetOptions,           // Puppet TOKEN
  ioToken?       : string,                  // Io TOKEN
103
}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
104

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
105
/**
L
lijiarui 已提交
106
 * Main bot class.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107
 *
L
lijiarui 已提交
108 109 110 111 112
 * A `Bot` is a wechat client depends on which puppet you use.
 * It may equals
 * - web-wechat, when you use: [puppet-puppeteer](https://github.com/chatie/wechaty-puppet-puppeteer)/[puppet-wechat4u](https://github.com/chatie/wechaty-puppet-wechat4u)
 * - ipad-wechat, when you use: [puppet-padchat](https://github.com/lijiarui/wechaty-puppet-padchat)
 * - ios-wechat, when you use: puppet-ioscat
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
113
 *
L
lijiarui 已提交
114 115
 * See more:
 * - [What is a Puppet in Wechaty](https://github.com/Chatie/wechaty-getting-started/wiki/FAQ-EN#31-what-is-a-puppet-in-wechaty)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
116
 *
L
lijiarui 已提交
117 118 119 120 121 122 123 124 125 126
 * > If you want to know how to send message, see [Message](#Message) <br>
 * > If you want to know how to get contact, see [Contact](#Contact)
 *
 * @example <caption>The World's Shortest ChatBot Code: 6 lines of JavaScript</caption>
 * const { Wechaty } = require('wechaty')
 * const bot = new Wechaty()
 * bot.on('scan',    (qrcode, status) => console.log(['https://api.qrserver.com/v1/create-qr-code/?data=',encodeURIComponent(qrcode),'&size=220x220&margin=20',].join('')))
 * bot.on('login',   user => console.log(`User ${user} logined`))
 * bot.on('message', message => console.log(`Message: ${message}`))
 * bot.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
127
 */
128
export class Wechaty extends Accessory implements Sayable {
129

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
130 131
  public readonly VERSION = VERSION

132 133
  public  readonly state      : StateSwitch
  private readonly readyState : StateSwitch
134

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
135
  /**
136
   * singleton globalInstance
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
137
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
138
   */
139
  private static globalInstance: Wechaty
140

141
  private readonly memory : MemoryCard
142

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
143 144
  private lifeTimer? : NodeJS.Timer
  private io?        : Io
145

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
146
  /**
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
147
   * the cuid
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
148
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
149
   */
150
  public readonly id : string
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
151

152
  public readonly Contact       : typeof Contact
153
  public readonly ContactSelf   : typeof ContactSelf
154
  public readonly Friendship    : typeof Friendship
155
  public readonly Message       : typeof Message
156
  public readonly RoomInvitation: typeof RoomInvitation
157
  public readonly Room          : typeof Room
158

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
159
  /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
160
   * Get the global instance of Wechaty
L
lijiarui 已提交
161
   *
L
lijiarui 已提交
162 163
   * @param {WechatyOptions} [options={}]
   *
L
lijiarui 已提交
164 165 166
   * @example <caption>The World's Shortest ChatBot Code: 6 lines of JavaScript</caption>
   * const { Wechaty } = require('wechaty')
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
167
   * Wechaty.instance() // Global instance
L
lijiarui 已提交
168 169 170
   * .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}`))
L
lijiarui 已提交
171
   * .start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
172
   */
173
  public static instance (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
174 175
    options?: WechatyOptions,
  ) {
176
    if (options && this.globalInstance) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
177
      throw new Error('instance can be only inited once by options!')
178
    }
179 180
    if (!this.globalInstance) {
      this.globalInstance = new Wechaty(options)
181
    }
182
    return this.globalInstance
183 184
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
185
  /**
L
lijiarui 已提交
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
   * The term [Puppet](https://github.com/Chatie/wechaty/wiki/Puppet) in Wechaty is an Abstract Class for implementing protocol plugins.
   * The plugins are the component that helps Wechaty to control the Wechat(that's the reason we call it puppet).
   * The plugins are named XXXPuppet, for example:
   * - [PuppetPuppeteer](https://github.com/Chatie/wechaty-puppet-puppeteer):
   * - [PuppetPadchat](https://github.com/lijiarui/wechaty-puppet-padchat)
   *
   * @typedef    PuppetName
   * @property   {string}  wechat4u
   * The default puppet, using the [wechat4u](https://github.com/nodeWechat/wechat4u) to control the [WeChat Web API](https://wx.qq.com/) via a chrome browser.
   * @property   {string}  padchat
   * - Using the WebSocket protocol to connect with a Protocol Server for controlling the iPad Wechat program.
   * @property   {string}  puppeteer
   * - Using the [google puppeteer](https://github.com/GoogleChrome/puppeteer) to control the [WeChat Web API](https://wx.qq.com/) via a chrome browser.
   * @property   {string}  mock
   * - Using the mock data to mock wechat operation, just for test.
   */

  /**
   * The option parameter to create a wechaty instance
   *
   * @typedef    WechatyOptions
   * @property   {string}                 profile            -Wechaty Name. </br>
   *          When you set this: </br>
   *          `new Wechaty({profile: 'wechatyName'}) ` </br>
   *          it will generate a file called `wechatyName.memory-card.json`. </br>
   *          This file stores the bot's login information. </br>
   *          If the file is valid, the bot can auto login so you don't need to scan the qrcode to login again. </br>
   *          Also, you can set the environment variable for `WECHATY_PROFILE` to set this value when you start. </br>
   *          eg:  `WECHATY_PROFILE="your-cute-bot-name" node bot.js`
   * @property   {PuppetName | Puppet}    puppet             -Puppet name or instance
   * @property   {Partial<PuppetOptions>} puppetOptions      -Puppet TOKEN
   * @property   {string}                 ioToken            -Io TOKEN
   */

  /**
   * Creates an instance of Wechaty.
   * @param {WechatyOptions} [options={}]
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224
   */
225
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
226
    private options: WechatyOptions = {},
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227
  ) {
228
    super()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
229 230
    log.verbose('Wechaty', 'contructor()')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231
    options.profile = options.profile === null
232
                      ? null
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233
                      : (options.profile || config.default.DEFAULT_PROFILE)
234

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235
    this.id     = cuid()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
236
    this.memory = new MemoryCard(options.profile || undefined)
237 238 239

    this.state      = new StateSwitch('Wechaty', log)
    this.readyState = new StateSwitch('WechatyReady', log)
240 241

    /**
L
lijiarui 已提交
242
     * @ignore
243 244 245 246 247 248 249
     * Clone Classes for this bot and attach the `puppet` to the Class
     *
     *   https://stackoverflow.com/questions/36886082/abstract-constructor-type-in-typescript
     *   https://github.com/Microsoft/TypeScript/issues/5843#issuecomment-290972055
     *   https://github.com/Microsoft/TypeScript/issues/19197
     */
    // TODO: make Message & Room constructor private???
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
250 251 252 253 254
    this.Contact        = cloneClass(Contact)
    this.ContactSelf    = cloneClass(ContactSelf)
    this.Friendship     = cloneClass(Friendship)
    this.Message        = cloneClass(Message)
    this.Room           = cloneClass(Room)
255
    this.RoomInvitation = cloneClass(RoomInvitation)
256 257
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
258
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
259
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
260
   */
261
  public toString () {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
262 263 264 265 266
    if (!this.options) {
      return this.constructor.name
    }

    return [
267 268
      'Wechaty#',
      this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
269
      `<${this.options && this.options.puppet || ''}>`,
270
      `(${this.memory  && this.memory.name    || ''})`,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
271 272
    ].join('')
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
273

274 275 276 277 278 279
  public emit (event: 'dong'       , data?: string)                                                    : boolean
  public emit (event: 'error'      , error: Error)                                                     : boolean
  public emit (event: 'friendship' , friendship: Friendship)                                           : boolean
  public emit (event: 'heartbeat'  , data: any)                                                        : boolean
  public emit (event: 'login' | 'logout', user: ContactSelf)                                           : boolean
  public emit (event: 'message'    , message: Message)                                                 : boolean
280
  public emit (event: 'ready')                                                                         : boolean
281
  public emit (event: 'room-invite', roomInvitation: RoomInvitation)                                   : boolean
282 283 284 285
  public emit (event: 'room-join'  , room: Room, inviteeList : Contact[], inviter  : Contact)          : boolean
  public emit (event: 'room-leave' , room: Room, leaverList  : Contact[], remover? : Contact)          : boolean
  public emit (event: 'room-topic' , room: Room, newTopic: string, oldTopic: string, changer: Contact) : boolean
  public emit (event: 'scan'       , qrcode: string, status: number, data?: string)                    : boolean
286
  public emit (event: 'start' | 'stop')                                                                : boolean
287 288

  // guard for the above event: make sure it includes all the possible values
289
  public emit (event: never, listener: never): never
290

291
  public emit (
292 293 294 295 296 297
    event:   WechatyEventName,
    ...args: any[]
  ): boolean {
    return super.emit(event, ...args)
  }

298
  public on (event: 'dong'       , listener: string | ((this: Wechaty, data?: string) => void))                                                    : this
299 300 301
  public on (event: 'error'      , listener: string | ((this: Wechaty, error: Error) => void))                                                     : this
  public on (event: 'friendship' , listener: string | ((this: Wechaty, friendship: Friendship) => void))                                           : this
  public on (event: 'heartbeat'  , listener: string | ((this: Wechaty, data: any) => void))                                                        : this
302
  public on (event: 'login' | 'logout', listener: string | ((this: Wechaty, user: ContactSelf) => void))                                           : this
303
  public on (event: 'message'    , listener: string | ((this: Wechaty, message: Message) => void))                                                 : this
304
  public on (event: 'ready'      , listener: string | ((this: Wechaty) => void))                                                                   : this
305
  public on (event: 'room-invite', listener: string | ((this: Wechaty, roomInvitation: RoomInvitation) => void))                                   : this
306 307 308 309
  public on (event: 'room-join'  , listener: string | ((this: Wechaty, room: Room, inviteeList: Contact[],  inviter: Contact) => void))            : this
  public on (event: 'room-leave' , listener: string | ((this: Wechaty, room: Room, leaverList: Contact[], remover?: Contact) => void))             : this
  public on (event: 'room-topic' , listener: string | ((this: Wechaty, room: Room, newTopic: string, oldTopic: string, changer: Contact) => void)) : this
  public on (event: 'scan'       , listener: string | ((this: Wechaty, qrcode: string, status: number, data?: string) => void))                    : this
310
  public on (event: 'start' | 'stop', listener: string | ((this: Wechaty) => void))                                                                : this
311

312
  // guard for the above event: make sure it includes all the possible values
313
  public on (event: never, listener: never): never
L
lijiarui 已提交
314

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
315
  /**
L
lijiarui 已提交
316 317 318 319 320 321 322 323 324 325 326
   * @desc       Wechaty Class Event Type
   * @typedef    WechatyEventName
   * @property   {string}  error      - When the bot get error, there will be a Wechaty error event fired.
   * @property   {string}  login      - After the bot login full successful, the event login will be emitted, with a Contact of current logined user.
   * @property   {string}  logout     - Logout will be emitted when bot detected log out, with a Contact of the current login user.
   * @property   {string}  heartbeat  - Get bot's heartbeat.
   * @property   {string}  friend     - When someone sends you a friend request, there will be a Wechaty friend event fired.
   * @property   {string}  message    - Emit when there's a new message.
   * @property   {string}  room-join  - Emit when anyone join any room.
   * @property   {string}  room-topic - Get topic event, emitted when someone change room topic.
   * @property   {string}  room-leave - Emit when anyone leave the room.<br>
L
lijiarui 已提交
327
   * @property   {string}  room-invite - Emit when there is a room invitation, see more in  {@link RoomInvitation}
L
lijiarui 已提交
328
   *                                    If someone leaves the room by themselves, wechat will not notice other people in the room, so the bot will never get the "leave" event.
L
lijiarui 已提交
329 330
   * @property   {string}  scan       - A scan event will be emitted when the bot needs to show you a QR Code for scanning. </br>
   *                                    It is recommend to install qrcode-terminal(run `npm install qrcode-terminal`) in order to show qrcode in the terminal.
L
lijiarui 已提交
331 332 333 334 335 336
   */

  /**
   * @desc       Wechaty Class Event Function
   * @typedef    WechatyEventFunction
   * @property   {Function} error           -(this: Wechaty, error: Error) => void callback function
337 338
   * @property   {Function} login           -(this: Wechaty, user: ContactSelf)=> void
   * @property   {Function} logout          -(this: Wechaty, user: ContactSelf) => void
L
lijiarui 已提交
339 340 341 342 343 344 345 346 347 348 349 350
   * @property   {Function} scan            -(this: Wechaty, url: string, code: number) => void <br>
   * <ol>
   * <li>URL: {String} the QR code image URL</li>
   * <li>code: {Number} the scan status code. some known status of the code list here is:</li>
   * </ol>
   * <ul>
   * <li>0 initial_</li>
   * <li>200 login confirmed</li>
   * <li>201 scaned, wait for confirm</li>
   * <li>408 waits for scan</li>
   * </ul>
   * @property   {Function} heartbeat       -(this: Wechaty, data: any) => void
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
351
   * @property   {Function} friendship      -(this: Wechaty, friendship: Friendship) => void
L
lijiarui 已提交
352 353
   * @property   {Function} message         -(this: Wechaty, message: Message) => void
   * @property   {Function} room-join       -(this: Wechaty, room: Room, inviteeList: Contact[],  inviter: Contact) => void
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
354
   * @property   {Function} room-topic      -(this: Wechaty, room: Room, newTopic: string, oldTopic: string, changer: Contact) => void
L
lijiarui 已提交
355
   * @property   {Function} room-leave      -(this: Wechaty, room: Room, leaverList: Contact[]) => void
L
lijiarui 已提交
356 357
   * @property   {Function} room-invite     -(this: Wechaty, room: Room, leaverList: Contact[]) => void <br>
   *                                        see more in  {@link RoomInvitation}
L
lijiarui 已提交
358 359 360 361
   */

  /**
   * @listens Wechaty
362
   * @param   {WechatyEventName}      event      - Emit WechatyEvent
L
lijiarui 已提交
363 364
   * @param   {WechatyEventFunction}  listener   - Depends on the WechatyEvent
   *
L
lijiarui 已提交
365 366 367 368 369
   * @return  {Wechaty}                          - this for chaining,
   * see advanced {@link https://github.com/Chatie/wechaty-getting-started/wiki/FAQ-EN#36-why-wechatyonevent-listener-return-wechaty|chaining usage}
   *
   * @desc
   * When the bot get message, it will emit the following Event.
L
lijiarui 已提交
370
   *
L
lijiarui 已提交
371 372 373 374 375 376 377 378 379 380
   * You can do anything you want when in these events functions.
   * The main Event name as follows:
   * - **scan**: Emit when the bot needs to show you a QR Code for scanning. After scan the qrcode, you can login
   * - **login**: Emit when bot login full successful.
   * - **logout**: Emit when bot detected log out.
   * - **message**: Emit when there's a new message.
   *
   * see more in {@link WechatyEventName}
   *
   * @example <caption>Event:scan</caption>
L
lijiarui 已提交
381
   * // Scan Event will emit when the bot needs to show you a QR Code for scanning
L
lijiarui 已提交
382
   *
L
lijiarui 已提交
383
   * bot.on('scan', (url, code) => {
L
lijiarui 已提交
384 385 386 387
   *   console.log(`[${code}] Scan ${url} to login.` )
   * })
   *
   * @example <caption>Event:login </caption>
L
lijiarui 已提交
388
   * // Login Event will emit when bot login full successful.
L
lijiarui 已提交
389
   *
L
lijiarui 已提交
390
   * bot.on('login', (user) => {
L
lijiarui 已提交
391 392 393 394
   *   console.log(`user ${user} login`)
   * })
   *
   * @example <caption>Event:logout </caption>
L
lijiarui 已提交
395
   * // Logout Event will emit when bot detected log out.
L
lijiarui 已提交
396
   *
L
lijiarui 已提交
397
   * bot.on('logout', (user) => {
L
lijiarui 已提交
398 399 400 401
   *   console.log(`user ${user} logout`)
   * })
   *
   * @example <caption>Event:message </caption>
L
lijiarui 已提交
402
   * // Message Event will emit when there's a new message.
L
lijiarui 已提交
403
   *
L
lijiarui 已提交
404
   * wechaty.on('message', (message) => {
L
lijiarui 已提交
405 406 407
   *   console.log(`message ${message} received`)
   * })
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
408
   * @example <caption>Event:friendship </caption>
L
lijiarui 已提交
409
   * // Friendship Event will emit when got a new friend request, or friendship is confirmed.
L
lijiarui 已提交
410
   *
L
lijiarui 已提交
411
   * bot.on('friendship', (friendship) => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
412 413 414
   *   if(friendship.type() === Friendship.Type.RECEIVE){ // 1. receive new friendship request from new contact
   *     const contact = friendship.contact()
   *     let result = await friendship.accept()
L
lijiarui 已提交
415 416 417 418 419
   *       if(result){
   *         console.log(`Request from ${contact.name()} is accept succesfully!`)
   *       } else{
   *         console.log(`Request from ${contact.name()} failed to accept!`)
   *       }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
420
   * 	  } else if (friendship.type() === Friendship.Type.CONFIRM) { // 2. confirm friendship
L
lijiarui 已提交
421 422 423 424 425
   *       console.log(`new friendship confirmed with ${contact.name()}`)
   *    }
   *  })
   *
   * @example <caption>Event:room-join </caption>
L
lijiarui 已提交
426
   * // room-join Event will emit when someone join the room.
L
lijiarui 已提交
427
   *
L
lijiarui 已提交
428
   * bot.on('room-join', (room, inviteeList, inviter) => {
L
lijiarui 已提交
429 430 431 432 433
   *   const nameList = inviteeList.map(c => c.name()).join(',')
   *   console.log(`Room ${room.topic()} got new member ${nameList}, invited by ${inviter}`)
   * })
   *
   * @example <caption>Event:room-leave </caption>
L
lijiarui 已提交
434
   * // room-leave Event will emit when someone leave the room.
L
lijiarui 已提交
435
   *
L
lijiarui 已提交
436
   * bot.on('room-leave', (room, leaverList) => {
L
lijiarui 已提交
437 438 439 440 441
   *   const nameList = leaverList.map(c => c.name()).join(',')
   *   console.log(`Room ${room.topic()} lost member ${nameList}`)
   * })
   *
   * @example <caption>Event:room-topic </caption>
L
lijiarui 已提交
442
   * // room-topic Event will emit when someone change the room's topic.
L
lijiarui 已提交
443
   *
L
lijiarui 已提交
444
   * bot.on('room-topic', (room, topic, oldTopic, changer) => {
L
lijiarui 已提交
445 446
   *   console.log(`Room ${room.topic()} topic changed from ${oldTopic} to ${topic} by ${changer.name()}`)
   * })
L
lijiarui 已提交
447
   *
L
lijiarui 已提交
448 449 450 451 452 453 454 455 456 457 458 459
   * @example <caption>Event:room-invite, RoomInvitation has been encapsulated as a RoomInvitation Class. </caption>
   * // room-invite Event will emit when there's an room invitation.
   *
   * bot.on('room-invite', async roomInvitation => {
   *   try {
   *     console.log(`received room-invite event.`)
   *     await roomInvitation.accept()
   *   } catch (e) {
   *     console.error(e)
   *   }
   * }
   *
L
lijiarui 已提交
460
   * @example <caption>Event:error </caption>
L
lijiarui 已提交
461
   * // error Event will emit when there's an error occurred.
L
lijiarui 已提交
462 463 464 465
   *
   * bot.on('error', (error) => {
   *   console.error(error)
   * })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
466
   */
467
  public on (event: WechatyEventName, listener: string | ((...args: any[]) => any)): this {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
468
    log.verbose('Wechaty', 'on(%s, %s) registered',
469 470 471 472 473 474
                            event,
                            typeof listener === 'string'
                              ? listener
                              : typeof listener,
                )

475 476
    // DEPRECATED for 'friend' event
    if (event as any === 'friend') {
477
      log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
478 479 480
      if (typeof listener === 'function') {
        const oldListener = listener
        listener = (...args: any[]) => {
481
          log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
482 483 484 485 486
          oldListener.apply(this, args)
        }
      }
    }

487
    if (typeof listener === 'function') {
488
      this.addListenerFunction(event, listener)
489
    } else {
490
      this.addListenerModuleFile(event, listener)
491
    }
492
    return this
493 494
  }

495
  private addListenerModuleFile (event: WechatyEventName, modulePath: string): void {
496
    const absoluteFilename = callerResolve(modulePath, __filename)
497 498
    log.verbose('Wechaty', 'onModulePath() hotImport(%s)', absoluteFilename)

499
    hotImport(absoluteFilename)
500
      .then((func: AnyFunction) => super.on(event, (...args: any[]) => {
501 502 503 504 505 506 507 508 509 510 511 512 513
        try {
          func.apply(this, args)
        } catch (e) {
          log.error('Wechaty', 'onModulePath(%s, %s) listener exception: %s',
                                event, modulePath, e)
          this.emit('error', e)
        }
      }))
      .catch(e => {
        log.error('Wechaty', 'onModulePath(%s, %s) hotImport() exception: %s',
                              event, modulePath, e)
        this.emit('error', e)
      })
514 515 516 517

    if (isProduction()) {
      log.silly('Wechaty', 'addListenerModuleFile() disable watch for hotImport because NODE_ENV is production.')
      hotImport(absoluteFilename, false)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
518
        .catch(e => log.error('Wechaty', 'addListenerModuleFile() hotImport() rejection: %s', e))
519
    }
520 521
  }

522
  private addListenerFunction (event: WechatyEventName, listener: AnyFunction): void {
523 524 525 526 527 528 529 530 531 532 533 534
    log.verbose('Wechaty', 'onFunction(%s)', event)

    super.on(event, (...args: any[]) => {
      try {
        listener.apply(this, args)
      } catch (e) {
        log.error('Wechaty', 'onFunction(%s) listener exception: %s', event, e)
        this.emit('error', e)
      }
    })
  }

535
  private async initPuppet (): Promise<void> {
536
    log.verbose('Wechaty', 'initPuppet() %s', this.options.puppet || '')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
537

538 539 540 541 542 543 544 545 546 547 548 549
    let inited = false
    try {
      inited = !!this.puppet
    } catch (e) {
      inited = false
    }

    if (inited) {
      log.verbose('Wechaty', 'initPuppet(%s) had already been inited, no need to init twice', this.options.puppet)
      return
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
550
    const puppet       = this.options.puppet || config.systemPuppetName()
551
    const puppetMemory = this.memory.sub('puppet')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
552

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
553 554 555 556 557
    const puppetInstance = await PuppetManager.resolve({
      puppet,
      puppetOptions : this.options.puppetOptions,
      wechaty       : this,
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
558

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
559 560 561
    /**
     * Plug the Memory Card to Puppet
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
562 563
    puppetInstance.setMemory(puppetMemory)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
564 565
    this.initPuppetEventBridge(puppetInstance)
    this.initPuppetAccessory(puppetInstance)
566
  }
567

568
  protected initPuppetEventBridge (puppet: Puppet) {
569 570 571
    log.verbose('Wechaty', 'initPuppetEventBridge(%s)', puppet)

    const eventNameList: PuppetEventName[] = Object.keys(PUPPET_EVENT_DICT) as PuppetEventName[]
572 573 574 575
    for (const eventName of eventNameList) {
      log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', eventName)

      switch (eventName) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
576 577 578 579 580 581
        case 'dong':
          puppet.on('dong', data => {
            this.emit('dong', data)
          })
          break

582 583 584 585 586 587
        case 'error':
          puppet.on('error', error => {
            this.emit('error', new Error(error))
          })
          break

588 589 590 591 592
        case 'watchdog':
          puppet.on('watchdog', data => {
            /**
             * Use `watchdog` event from Puppet to `heartbeat` Wechaty.
             */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
593
            // TODO: use a throttle queue to prevent beat too fast.
594 595 596 597
            this.emit('heartbeat', data)
          })
          break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
598 599 600 601 602 603 604 605 606 607
        case 'friendship':
          puppet.on('friendship', async friendshipId => {
            const friendship = this.Friendship.load(friendshipId)
            await friendship.ready()
            this.emit('friendship', friendship)
            friendship.contact().emit('friendship', friendship)

            // support deprecated event name: friend.
            // Huan LI 201806
            this.emit('friend' as any, friendship as any)
608 609 610 611 612
          })
          break

        case 'login':
          puppet.on('login', async contactId => {
613
            const contact = this.ContactSelf.load(contactId)
614 615 616 617 618 619 620
            await contact.ready()
            this.emit('login', contact)
          })
          break

        case 'logout':
          puppet.on('logout', async contactId => {
621
            const contact = this.ContactSelf.load(contactId)
622 623 624 625 626 627 628 629 630 631 632 633 634
            await contact.ready()
            this.emit('logout', contact)
          })
          break

        case 'message':
          puppet.on('message', async messageId => {
            const msg = this.Message.create(messageId)
            await msg.ready()
            this.emit('message', msg)
          })
          break

635 636 637 638 639 640 641 642 643
        case 'ready':
          puppet.on('ready', () => {
            log.silly('Wechaty', 'initPuppetEventBridge() puppet.on(ready)')

            this.emit('ready')
            this.readyState.on(true)
          })
          break

644 645 646
        case 'room-invite':
          puppet.on('room-invite', async roomInvitationId => {
            const roomInvitation = this.RoomInvitation.load(roomInvitationId)
647
            this.emit('room-invite', roomInvitation)
648 649 650
          })
          break

651 652 653 654 655 656 657 658 659 660 661 662
        case 'room-join':
          puppet.on('room-join', async (roomId, inviteeIdList, inviterId) => {
            const room = this.Room.load(roomId)
            await room.ready()

            const inviteeList = inviteeIdList.map(id => this.Contact.load(id))
            await Promise.all(inviteeList.map(c => c.ready()))

            const inviter = this.Contact.load(inviterId)
            await inviter.ready()

            this.emit('room-join', room, inviteeList, inviter)
663
            room.emit('join', inviteeList, inviter)
664 665 666 667
          })
          break

        case 'room-leave':
668
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
669 670 671 672 673 674
            const room = this.Room.load(roomId)
            await room.ready()

            const leaverList = leaverIdList.map(id => this.Contact.load(id))
            await Promise.all(leaverList.map(c => c.ready()))

675
            let remover: undefined | Contact
676 677 678 679 680 681
            if (removerId) {
              remover = this.Contact.load(removerId)
              await remover.ready()
            }

            this.emit('room-leave', room, leaverList, remover)
682
            room.emit('leave', leaverList, remover)
683 684 685 686
          })
          break

        case 'room-topic':
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
687
          puppet.on('room-topic', async (roomId, newTopic, oldTopic, changerId) => {
688 689 690 691 692 693
            const room = this.Room.load(roomId)
            await room.ready()

            const changer = this.Contact.load(changerId)
            await changer.ready()

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
694 695
            this.emit('room-topic', room, newTopic, oldTopic, changer)
            room.emit('topic', newTopic, oldTopic, changer)
696 697 698 699
          })
          break

        case 'scan':
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
700 701
          puppet.on('scan', async (qrcode, status, data) => {
            this.emit('scan', qrcode, status, data)
702 703 704
          })
          break

705
        case 'watchdog':
706
        case 'reset':
707 708
          break

709
        default:
710
          throw new Error('eventName ' + eventName + ' unsupported!')
711 712

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
713
    }
714
  }
715

716
  protected initPuppetAccessory (puppet: Puppet) {
717
    log.verbose('Wechaty', 'initAccessory(%s)', puppet)
718

719
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
720
     * 1. Set Wechaty
721
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
722 723 724 725 726
    this.Contact.wechaty        = this
    this.ContactSelf.wechaty    = this
    this.Friendship.wechaty     = this
    this.Message.wechaty        = this
    this.Room.wechaty           = this
727
    this.RoomInvitation.wechaty = this
728

729
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
730
     * 2. Set Puppet
731
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
732 733 734 735 736
    this.Contact.puppet        = puppet
    this.ContactSelf.puppet    = puppet
    this.Friendship.puppet     = puppet
    this.Message.puppet        = puppet
    this.Room.puppet           = puppet
737
    this.RoomInvitation.puppet = puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
738

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
739
    this.puppet = puppet
740 741
  }

742
  /**
L
lijiarui 已提交
743 744 745
   * @desc
   * use {@link Wechaty#start} instead
   * @deprecated
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
746
   * @private
747
   */
748
  public async init (): Promise<void> {
749 750 751
    log.warn('Wechaty', 'init() DEPRECATED. use start() instead.')
    return this.start()
  }
752 753 754 755
  /**
   * Start the bot, return Promise.
   *
   * @returns {Promise<void>}
L
lijiarui 已提交
756 757 758
   * @description
   * When you start the bot, bot will begin to login, need you wechat scan qrcode to login
   * > Tips: All the bot operation needs to be triggered after start() is done
759 760 761 762
   * @example
   * await bot.start()
   * // do other stuff with bot here
   */
763
  public async start (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
764 765 766 767
    log.info('Wechaty', '<%s> start() v%s is starting...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
768 769
    log.verbose('Wechaty', 'puppet: %s'   , this.options.puppet)
    log.verbose('Wechaty', 'profile: %s'  , this.options.profile)
770
    log.verbose('Wechaty', 'id: %s'       , this.id)
771 772 773

    if (this.state.on()) {
      log.silly('Wechaty', 'start() on a starting/started instance')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
774
      await this.state.ready('on')
775 776 777 778
      log.silly('Wechaty', 'start() state.ready() resolved')
      return
    }

779 780
    this.readyState.off(true)

781 782 783 784
    if (this.lifeTimer) {
      throw new Error('start() lifeTimer exist')
    }

785 786 787
    this.state.on('pending')

    try {
788
      await this.memory.load()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
789

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
790
      await this.initPuppet()
791 792
      await this.puppet.start()

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
793 794 795 796 797 798 799 800
      if (this.options.ioToken) {
        this.io = new Io({
          token   : this.options.ioToken,
          wechaty : this,
        })
        await this.io.start()
      }

801
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
802
      console.error(e)
803 804
      log.error('Wechaty', 'start() exception: %s', e && e.message)
      Raven.captureException(e)
805 806 807 808 809 810 811 812 813
      this.emit('error', e)

      try {
        await this.stop()
      } catch (e) {
        log.error('Wechaty', 'start() stop() exception: %s', e && e.message)
        Raven.captureException(e)
        this.emit('error', e)
      }
814
      return
815 816 817 818
    }

    this.on('heartbeat', () => this.memoryCheck())

819 820 821 822
    this.lifeTimer = setInterval(() => {
      log.silly('Wechaty', 'start() setInterval() this timer is to keep Wechaty running...')
    }, 1000 * 60 * 60)

823 824 825 826 827 828
    this.state.on(true)
    this.emit('start')

    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
829 830 831 832 833 834 835
  /**
   * Stop the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.stop()
   */
836
  public async stop (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
837 838 839 840
    log.info('Wechaty', '<%s> stop() v%s is stoping ...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
841

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
842
    if (this.state.off()) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
843 844 845
      log.silly('Wechaty', 'stop() on an stopping/stopped instance')
      await this.state.ready('off')
      log.silly('Wechaty', 'stop() state.ready(off) resolved')
846
      return
847
    }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
848

849 850
    this.readyState.off(true)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
851
    this.state.off('pending')
852
    await this.memory.save()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
853

854 855 856 857 858
    if (this.lifeTimer) {
      clearInterval(this.lifeTimer)
      this.lifeTimer = undefined
    }

859
    try {
860
      await this.puppet.stop()
861 862 863
    } catch (e) {
      log.warn('Wechaty', 'stop() puppet.stop() exception: %s', e.message)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
864

865
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
866 867 868 869 870
      if (this.io) {
        await this.io.stop()
        this.io = undefined
      }

871
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
872
      log.error('Wechaty', 'stop() exception: %s', e.message)
873
      Raven.captureException(e)
874
      this.emit('error', e)
875
    }
876 877 878 879

    this.state.off(true)
    this.emit('stop')

880
    return
881
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
882

883 884 885 886 887 888 889
  public async ready (): Promise<void> {
    log.verbose('Wechaty', 'ready()')
    return this.readyState.ready('on').then(() => {
      log.silly('Wechaty', 'ready() this.readyState.ready(on) resolved')
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
890
  /**
L
lijiarui 已提交
891 892 893 894 895
   * Logout the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.logout()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
896
   */
897
  public async logout (): Promise<void>  {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
898 899
    log.verbose('Wechaty', 'logout()')

900 901 902 903 904 905 906
    try {
      await this.puppet.logout()
    } catch (e) {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      Raven.captureException(e)
      throw e
    }
907
    return
908
  }
909

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
910 911 912 913 914
  /**
   * Get the logon / logoff state
   *
   * @returns {boolean}
   * @example
915
   * if (bot.logonoff()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
916 917 918 919 920
   *   console.log('Bot logined')
   * } else {
   *   console.log('Bot not logined')
   * }
   */
921
  public logonoff (): boolean {
922
    return this.puppet.logonoff()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
923 924
  }

925
  /**
L
lijiarui 已提交
926 927
   * @description
   * Should use {@link Wechaty#userSelf} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
928
   * @deprecated Use `userSelf()` instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
929
   * @private
930
   */
931
  public self (): Contact {
932
    log.warn('Wechaty', 'self() DEPRECATED. use userSelf() instead.')
933 934 935
    return this.userSelf()
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
936
  /**
L
lijiarui 已提交
937 938
   * Get current user
   *
939
   * @returns {ContactSelf}
L
lijiarui 已提交
940
   * @example
941
   * const contact = bot.userSelf()
L
lijiarui 已提交
942
   * console.log(`Bot is ${contact.name()}`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
943
   */
944
  public userSelf (): ContactSelf {
945
    const userId = this.puppet.selfId()
946
    const user = this.ContactSelf.load(userId)
947
    return user
948
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
949

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
950 951 952 953 954
  public async say (text: string)     : Promise<void>
  public async say (contact: Contact) : Promise<void>
  public async say (file: FileBox)    : Promise<void>

  public async say (...args: never[]): Promise<never>
955

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
956
  /**
L
lijiarui 已提交
957
   * Send message to userSelf, in other words, bot send message to itself.
958 959 960
   * > Tips:
   * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/Chatie/wechaty/wiki/Puppet#3-puppet-compatible-table)
   *
L
lijiarui 已提交
961 962 963
   * @param {(string | Contact | FileBox)} textOrContactOrFile
   * send text, Contact, or file to bot. </br>
   * You can use {@link https://www.npmjs.com/package/file-box|FileBox} to send file
964
   *
L
lijiarui 已提交
965
   * @returns {Promise<void>}
L
lijiarui 已提交
966
   *
L
lijiarui 已提交
967 968 969 970 971
   * @example
   * const bot = new Wechaty()
   * await bot.start()
   * // after logged in
   *
L
lijiarui 已提交
972
   * // 1. send text to bot itself
L
lijiarui 已提交
973 974
   * await bot.say('hello!')
   *
L
lijiarui 已提交
975
   * // 2. send Contact to bot itself
L
lijiarui 已提交
976 977 978
   * const contact = bot.Contact.load('contactId')
   * await bot.say(contact)
   *
L
lijiarui 已提交
979
   * // 3. send Image to bot itself from remote url
L
lijiarui 已提交
980 981 982 983
   * import { FileBox }  from 'file-box'
   * const fileBox = FileBox.fromUrl('https://chatie.io/wechaty/images/bot-qr-code.png')
   * await bot.say(fileBox)
   *
L
lijiarui 已提交
984
   * // 4. send Image to bot itself from local file
L
lijiarui 已提交
985
   * import { FileBox }  from 'file-box'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
986
   * const fileBox = FileBox.fromFile('/tmp/text.jpg')
L
lijiarui 已提交
987
   * await bot.say(fileBox)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
988
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
989

990
  public async say (textOrContactOrFile: string | Contact | FileBox): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
991 992 993 994 995 996 997 998 999 1000 1001 1002
    log.verbose('Wechaty', 'say(%s)', textOrContactOrFile)

    // Make Typescript Happy:
    if (typeof textOrContactOrFile === 'string') {
      await this.userSelf().say(textOrContactOrFile)
    } else if (textOrContactOrFile instanceof Contact) {
      await this.userSelf().say(textOrContactOrFile)
    } else if (textOrContactOrFile instanceof FileBox) {
      await this.userSelf().say(textOrContactOrFile)
    } else {
      throw new Error('unsupported')
    }
1003 1004
  }

L
lijiarui 已提交
1005 1006 1007
  /**
   * @private
   */
1008
  public static version (forceNpm = false): string {
L
lijiarui 已提交
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
    if (!forceNpm) {
      const revision = config.gitRevision()
      if (revision) {
        return `#git[${revision}]`
      }
    }
    return VERSION
  }

 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1019
  * @private
L
lijiarui 已提交
1020 1021 1022 1023 1024 1025 1026 1027 1028
  * Return version of Wechaty
  *
  * @param {boolean} [forceNpm=false]  - If set to true, will only return the version in package.json. </br>
  *                                      Otherwise will return git commit hash if .git exists.
  * @returns {string}                  - the version number
  * @example
  * console.log(Wechaty.instance().version())       // return '#git[af39df]'
  * console.log(Wechaty.instance().version(true))   // return '0.7.9'
  */
1029
  public version (forceNpm = false): string {
L
lijiarui 已提交
1030 1031 1032
    return Wechaty.version(forceNpm)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1033
  /**
L
lijiarui 已提交
1034
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1035
   */
1036
  public static async sleep (millisecond: number): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1037
    await new Promise(resolve => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1038 1039 1040 1041
      setTimeout(resolve, millisecond)
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1042
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
1043
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1044
   */
1045
  public ding (data?: string): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1046 1047
    log.silly('Wechaty', 'ding(%s)', data || '')

1048
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1049
      this.puppet.ding(data)
1050 1051 1052
    } catch (e) {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      Raven.captureException(e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1053
      throw e
1054
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1055
  }
1056 1057 1058 1059

  /**
   * @private
   */
1060
  private memoryCheck (minMegabyte = 4): void {
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
    const freeMegabyte = Math.floor(os.freemem() / 1024 / 1024)
    log.silly('Wechaty', 'memoryCheck() free: %d MB, require: %d MB',
                          freeMegabyte, minMegabyte)

    if (freeMegabyte < minMegabyte) {
      const e = new Error(`memory not enough: free ${freeMegabyte} < require ${minMegabyte} MB`)
      log.warn('Wechaty', 'memoryCheck() %s', e.message)
      this.emit('error', e)
    }
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1071 1072 1073 1074

  /**
   * @private
   */
1075
  public async reset (reason?: string): Promise<void> {
1076
    log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
1077 1078
    await this.puppet.stop()
    await this.puppet.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1079 1080 1081
    return
  }

1082
  public unref (): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1083 1084 1085 1086 1087 1088 1089
    log.verbose('Wechaty', 'unref()')

    if (this.lifeTimer) {
      this.lifeTimer.unref()
    }

    this.puppet.unref()
1090
  }
1091
}