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 106
const PUPPET_MEMORY_NAME = 'puppet'

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107
/**
L
lijiarui 已提交
108
 * Main bot class.
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
109
 *
L
lijiarui 已提交
110 111 112 113 114
 * 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 (李卓桓) 已提交
115
 *
L
lijiarui 已提交
116 117
 * 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 (李卓桓) 已提交
118
 *
L
lijiarui 已提交
119 120 121 122 123 124 125 126 127 128
 * > 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 (李卓桓) 已提交
129
 */
130
export class Wechaty extends Accessory implements Sayable {
131

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

134 135
  public  readonly state      : StateSwitch
  private readonly readyState : StateSwitch
136

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

143
  private readonly memory : MemoryCard
144

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

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

154
  public readonly Contact       : typeof Contact
155
  public readonly ContactSelf   : typeof ContactSelf
156
  public readonly Friendship    : typeof Friendship
157
  public readonly Message       : typeof Message
158
  public readonly RoomInvitation: typeof RoomInvitation
159
  public readonly Room          : typeof Room
160

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
187
  /**
L
lijiarui 已提交
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 224 225
   * 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 (李卓桓) 已提交
226
   */
227
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
228
    private options: WechatyOptions = {},
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
229
  ) {
230
    super()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
231 232
    log.verbose('Wechaty', 'contructor()')

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

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

    this.state      = new StateSwitch('Wechaty', log)
    this.readyState = new StateSwitch('WechatyReady', log)
242 243

    /**
L
lijiarui 已提交
244
     * @ignore
245 246 247 248 249 250 251
     * 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 (李卓桓) 已提交
252 253 254 255 256
    this.Contact        = cloneClass(Contact)
    this.ContactSelf    = cloneClass(ContactSelf)
    this.Friendship     = cloneClass(Friendship)
    this.Message        = cloneClass(Message)
    this.Room           = cloneClass(Room)
257
    this.RoomInvitation = cloneClass(RoomInvitation)
258 259
  }

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

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

276 277 278 279 280 281
  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
282
  public emit (event: 'ready')                                                                         : boolean
283
  public emit (event: 'room-invite', roomInvitation: RoomInvitation)                                   : boolean
284 285 286 287
  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
288
  public emit (event: 'start' | 'stop')                                                                : boolean
289 290

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

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

300
  public on (event: 'dong'       , listener: string | ((this: Wechaty, data?: string) => void))                                                    : this
301 302 303
  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
304
  public on (event: 'login' | 'logout', listener: string | ((this: Wechaty, user: ContactSelf) => void))                                           : this
305
  public on (event: 'message'    , listener: string | ((this: Wechaty, message: Message) => void))                                                 : this
306
  public on (event: 'ready'      , listener: string | ((this: Wechaty) => void))                                                                   : this
307
  public on (event: 'room-invite', listener: string | ((this: Wechaty, roomInvitation: RoomInvitation) => void))                                   : this
308 309 310 311
  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
312
  public on (event: 'start' | 'stop', listener: string | ((this: Wechaty) => void))                                                                : this
313

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
317
  /**
L
lijiarui 已提交
318 319 320 321 322 323 324 325 326 327 328
   * @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 已提交
329
   * @property   {string}  room-invite - Emit when there is a room invitation, see more in  {@link RoomInvitation}
L
lijiarui 已提交
330
   *                                    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 已提交
331 332
   * @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 已提交
333 334 335 336 337 338
   */

  /**
   * @desc       Wechaty Class Event Function
   * @typedef    WechatyEventFunction
   * @property   {Function} error           -(this: Wechaty, error: Error) => void callback function
339 340
   * @property   {Function} login           -(this: Wechaty, user: ContactSelf)=> void
   * @property   {Function} logout          -(this: Wechaty, user: ContactSelf) => void
L
lijiarui 已提交
341 342 343 344 345 346 347 348 349 350 351 352
   * @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 (李卓桓) 已提交
353
   * @property   {Function} friendship      -(this: Wechaty, friendship: Friendship) => void
L
lijiarui 已提交
354 355
   * @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 (李卓桓) 已提交
356
   * @property   {Function} room-topic      -(this: Wechaty, room: Room, newTopic: string, oldTopic: string, changer: Contact) => void
L
lijiarui 已提交
357
   * @property   {Function} room-leave      -(this: Wechaty, room: Room, leaverList: Contact[]) => void
L
lijiarui 已提交
358 359
   * @property   {Function} room-invite     -(this: Wechaty, room: Room, leaverList: Contact[]) => void <br>
   *                                        see more in  {@link RoomInvitation}
L
lijiarui 已提交
360 361 362 363
   */

  /**
   * @listens Wechaty
364
   * @param   {WechatyEventName}      event      - Emit WechatyEvent
L
lijiarui 已提交
365 366
   * @param   {WechatyEventFunction}  listener   - Depends on the WechatyEvent
   *
L
lijiarui 已提交
367 368 369 370 371
   * @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 已提交
372
   *
L
lijiarui 已提交
373 374 375 376 377 378 379 380 381 382
   * 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 已提交
383
   * // Scan Event will emit when the bot needs to show you a QR Code for scanning
L
lijiarui 已提交
384
   *
L
lijiarui 已提交
385
   * bot.on('scan', (url, code) => {
L
lijiarui 已提交
386 387 388 389
   *   console.log(`[${code}] Scan ${url} to login.` )
   * })
   *
   * @example <caption>Event:login </caption>
L
lijiarui 已提交
390
   * // Login Event will emit when bot login full successful.
L
lijiarui 已提交
391
   *
L
lijiarui 已提交
392
   * bot.on('login', (user) => {
L
lijiarui 已提交
393 394 395 396
   *   console.log(`user ${user} login`)
   * })
   *
   * @example <caption>Event:logout </caption>
L
lijiarui 已提交
397
   * // Logout Event will emit when bot detected log out.
L
lijiarui 已提交
398
   *
L
lijiarui 已提交
399
   * bot.on('logout', (user) => {
L
lijiarui 已提交
400 401 402 403
   *   console.log(`user ${user} logout`)
   * })
   *
   * @example <caption>Event:message </caption>
L
lijiarui 已提交
404
   * // Message Event will emit when there's a new message.
L
lijiarui 已提交
405
   *
L
lijiarui 已提交
406
   * wechaty.on('message', (message) => {
L
lijiarui 已提交
407 408 409
   *   console.log(`message ${message} received`)
   * })
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
410
   * @example <caption>Event:friendship </caption>
L
lijiarui 已提交
411
   * // Friendship Event will emit when got a new friend request, or friendship is confirmed.
L
lijiarui 已提交
412
   *
L
lijiarui 已提交
413
   * bot.on('friendship', (friendship) => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
414 415 416
   *   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 已提交
417 418 419 420 421
   *       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 (李卓桓) 已提交
422
   * 	  } else if (friendship.type() === Friendship.Type.CONFIRM) { // 2. confirm friendship
L
lijiarui 已提交
423 424 425 426 427
   *       console.log(`new friendship confirmed with ${contact.name()}`)
   *    }
   *  })
   *
   * @example <caption>Event:room-join </caption>
L
lijiarui 已提交
428
   * // room-join Event will emit when someone join the room.
L
lijiarui 已提交
429
   *
L
lijiarui 已提交
430
   * bot.on('room-join', (room, inviteeList, inviter) => {
L
lijiarui 已提交
431 432 433 434 435
   *   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 已提交
436
   * // room-leave Event will emit when someone leave the room.
L
lijiarui 已提交
437
   *
L
lijiarui 已提交
438
   * bot.on('room-leave', (room, leaverList) => {
L
lijiarui 已提交
439 440 441 442 443
   *   const nameList = leaverList.map(c => c.name()).join(',')
   *   console.log(`Room ${room.topic()} lost member ${nameList}`)
   * })
   *
   * @example <caption>Event:room-topic </caption>
L
lijiarui 已提交
444
   * // room-topic Event will emit when someone change the room's topic.
L
lijiarui 已提交
445
   *
L
lijiarui 已提交
446
   * bot.on('room-topic', (room, topic, oldTopic, changer) => {
L
lijiarui 已提交
447 448
   *   console.log(`Room ${room.topic()} topic changed from ${oldTopic} to ${topic} by ${changer.name()}`)
   * })
L
lijiarui 已提交
449
   *
L
lijiarui 已提交
450 451 452 453 454 455 456 457 458 459 460 461
   * @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 已提交
462
   * @example <caption>Event:error </caption>
L
lijiarui 已提交
463
   * // error Event will emit when there's an error occurred.
L
lijiarui 已提交
464 465 466 467
   *
   * bot.on('error', (error) => {
   *   console.error(error)
   * })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
468
   */
469
  public on (event: WechatyEventName, listener: string | ((...args: any[]) => any)): this {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
470
    log.verbose('Wechaty', 'on(%s, %s) registered',
471 472 473 474 475 476
                            event,
                            typeof listener === 'string'
                              ? listener
                              : typeof listener,
                )

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

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

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

501
    hotImport(absoluteFilename)
502
      .then((func: AnyFunction) => super.on(event, (...args: any[]) => {
503 504 505 506 507 508 509 510 511 512 513 514 515
        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)
      })
516 517 518 519

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

524
  private addListenerFunction (event: WechatyEventName, listener: AnyFunction): void {
525 526 527 528 529 530 531 532 533 534 535 536
    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)
      }
    })
  }

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

540 541 542 543 544 545 546 547 548 549 550 551
    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 (李卓桓) 已提交
552
    const puppet       = this.options.puppet || config.systemPuppetName()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
553
    const puppetMemory = this.memory.sub(PUPPET_MEMORY_NAME)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
554

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

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

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

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

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

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
600 601 602 603 604 605 606 607 608 609
        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)
610 611 612 613 614
          })
          break

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

        case 'logout':
          puppet.on('logout', async contactId => {
623
            const contact = this.ContactSelf.load(contactId)
624 625 626 627 628 629 630 631 632 633 634 635 636
            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

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

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

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

653 654 655 656 657 658 659 660 661 662 663 664
        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)
665
            room.emit('join', inviteeList, inviter)
666 667 668 669
          })
          break

        case 'room-leave':
670
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
671 672 673 674 675 676
            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()))

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

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

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

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

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

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

707
        case 'watchdog':
708
        case 'reset':
709 710
          break

711
        default:
712
          throw new Error('eventName ' + eventName + ' unsupported!')
713 714

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
715
    }
716
  }
717

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

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

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

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

744
  /**
L
lijiarui 已提交
745 746 747
   * @desc
   * use {@link Wechaty#start} instead
   * @deprecated
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
748
   * @private
749
   */
750
  public async init (): Promise<void> {
751 752 753
    log.warn('Wechaty', 'init() DEPRECATED. use start() instead.')
    return this.start()
  }
754 755 756 757
  /**
   * Start the bot, return Promise.
   *
   * @returns {Promise<void>}
L
lijiarui 已提交
758 759 760
   * @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
761 762 763 764
   * @example
   * await bot.start()
   * // do other stuff with bot here
   */
765
  public async start (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
766 767 768 769
    log.info('Wechaty', '<%s> start() v%s is starting...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
770 771
    log.verbose('Wechaty', 'puppet: %s'   , this.options.puppet)
    log.verbose('Wechaty', 'profile: %s'  , this.options.profile)
772
    log.verbose('Wechaty', 'id: %s'       , this.id)
773 774 775

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

781 782
    this.readyState.off(true)

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

787 788 789
    this.state.on('pending')

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

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

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

803
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
804
      console.error(e)
805 806
      log.error('Wechaty', 'start() exception: %s', e && e.message)
      Raven.captureException(e)
807 808 809 810 811 812 813 814 815
      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)
      }
816
      return
817 818 819 820
    }

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

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

825 826 827 828 829 830
    this.state.on(true)
    this.emit('start')

    return
  }

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

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

851 852
    this.readyState.off(true)

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

856 857 858 859 860
    if (this.lifeTimer) {
      clearInterval(this.lifeTimer)
      this.lifeTimer = undefined
    }

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

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

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

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

882
    return
883
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
884

885 886 887 888 889 890 891
  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 (李卓桓) 已提交
892
  /**
L
lijiarui 已提交
893 894 895 896 897
   * Logout the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.logout()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
898
   */
899
  public async logout (): Promise<void>  {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
900 901
    log.verbose('Wechaty', 'logout()')

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

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
952 953 954 955 956
  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>
957

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
958
  /**
L
lijiarui 已提交
959
   * Send message to userSelf, in other words, bot send message to itself.
960 961 962
   * > 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 已提交
963 964 965
   * @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
966
   *
L
lijiarui 已提交
967
   * @returns {Promise<void>}
L
lijiarui 已提交
968
   *
L
lijiarui 已提交
969 970 971 972 973
   * @example
   * const bot = new Wechaty()
   * await bot.start()
   * // after logged in
   *
L
lijiarui 已提交
974
   * // 1. send text to bot itself
L
lijiarui 已提交
975 976
   * await bot.say('hello!')
   *
L
lijiarui 已提交
977
   * // 2. send Contact to bot itself
L
lijiarui 已提交
978 979 980
   * const contact = bot.Contact.load('contactId')
   * await bot.say(contact)
   *
L
lijiarui 已提交
981
   * // 3. send Image to bot itself from remote url
L
lijiarui 已提交
982 983 984 985
   * import { FileBox }  from 'file-box'
   * const fileBox = FileBox.fromUrl('https://chatie.io/wechaty/images/bot-qr-code.png')
   * await bot.say(fileBox)
   *
L
lijiarui 已提交
986
   * // 4. send Image to bot itself from local file
L
lijiarui 已提交
987
   * import { FileBox }  from 'file-box'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
988
   * const fileBox = FileBox.fromFile('/tmp/text.jpg')
L
lijiarui 已提交
989
   * await bot.say(fileBox)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
990
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
991

992
  public async say (textOrContactOrFile: string | Contact | FileBox): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
993 994 995 996 997 998 999 1000 1001 1002 1003 1004
    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')
    }
1005 1006
  }

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

 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1021
  * @private
L
lijiarui 已提交
1022 1023 1024 1025 1026 1027 1028 1029 1030
  * 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'
  */
1031
  public version (forceNpm = false): string {
L
lijiarui 已提交
1032 1033 1034
    return Wechaty.version(forceNpm)
  }

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

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

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

  /**
   * @private
   */
1062
  private memoryCheck (minMegabyte = 4): void {
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
    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 (李卓桓) 已提交
1073 1074 1075 1076

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

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

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

    this.puppet.unref()
1092
  }
1093
}