wechaty.ts 37.1 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 103
  memory?        : MemoryCard,
  profile?       : null | string,         // Wechaty Name
  puppet?        : PuppetName | Puppet,   // Puppet name or instance
  puppetOptions? : PuppetOptions,         // Puppet TOKEN
  ioToken?       : string,                // Io TOKEN
104
}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
105

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
106 107
const PUPPET_MEMORY_NAME = 'puppet'

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

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

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

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

144
  private readonly memory : MemoryCard
145

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

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

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

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
238
    this.id     = cuid()
239 240 241 242 243 244 245 246

    this.memory = options.memory
      ? options.memory
      : new MemoryCard(
          options.profile
          ? { name: options.profile }
          : undefined,
        )
247 248 249

    this.state      = new StateSwitch('Wechaty', log)
    this.readyState = new StateSwitch('WechatyReady', log)
250 251

    /**
L
lijiarui 已提交
252
     * @ignore
253 254 255 256 257 258 259
     * 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 (李卓桓) 已提交
260 261 262 263 264
    this.Contact        = cloneClass(Contact)
    this.ContactSelf    = cloneClass(ContactSelf)
    this.Friendship     = cloneClass(Friendship)
    this.Message        = cloneClass(Message)
    this.Room           = cloneClass(Room)
265
    this.RoomInvitation = cloneClass(RoomInvitation)
266 267
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
268
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
269
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
270
   */
271
  public toString () {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
272 273 274 275 276
    if (!this.options) {
      return this.constructor.name
    }

    return [
277 278
      'Wechaty#',
      this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
279
      `<${this.options && this.options.puppet || ''}>`,
280
      `(${this.memory.options && this.memory.options.name || ''})`,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281 282
    ].join('')
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
283

284 285 286 287 288 289
  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
290
  public emit (event: 'ready')                                                                         : boolean
291
  public emit (event: 'room-invite', roomInvitation: RoomInvitation)                                   : boolean
292 293 294 295
  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
296
  public emit (event: 'start' | 'stop')                                                                : boolean
297 298

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

301
  public emit (
302 303 304 305 306 307
    event:   WechatyEventName,
    ...args: any[]
  ): boolean {
    return super.emit(event, ...args)
  }

308
  public on (event: 'dong'       , listener: string | ((this: Wechaty, data?: string) => void))                                                    : this
309 310 311
  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
312
  public on (event: 'login' | 'logout', listener: string | ((this: Wechaty, user: ContactSelf) => void))                                           : this
313
  public on (event: 'message'    , listener: string | ((this: Wechaty, message: Message) => void))                                                 : this
314
  public on (event: 'ready'      , listener: string | ((this: Wechaty) => void))                                                                   : this
315
  public on (event: 'room-invite', listener: string | ((this: Wechaty, roomInvitation: RoomInvitation) => void))                                   : this
316 317 318 319
  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
320
  public on (event: 'start' | 'stop', listener: string | ((this: Wechaty) => void))                                                                : this
321

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
325
  /**
L
lijiarui 已提交
326 327 328 329 330 331 332 333 334 335 336
   * @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 已提交
337
   * @property   {string}  room-invite - Emit when there is a room invitation, see more in  {@link RoomInvitation}
L
lijiarui 已提交
338
   *                                    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 已提交
339 340
   * @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 已提交
341 342 343 344 345 346
   */

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

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

485 486
    // DEPRECATED for 'friend' event
    if (event as any === 'friend') {
487
      log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
488 489 490
      if (typeof listener === 'function') {
        const oldListener = listener
        listener = (...args: any[]) => {
491
          log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
492 493 494 495 496
          oldListener.apply(this, args)
        }
      }
    }

497
    if (typeof listener === 'function') {
498
      this.addListenerFunction(event, listener)
499
    } else {
500
      this.addListenerModuleFile(event, listener)
501
    }
502
    return this
503 504
  }

505
  private addListenerModuleFile (event: WechatyEventName, modulePath: string): void {
506
    const absoluteFilename = callerResolve(modulePath, __filename)
507 508
    log.verbose('Wechaty', 'onModulePath() hotImport(%s)', absoluteFilename)

509
    hotImport(absoluteFilename)
510
      .then((func: AnyFunction) => super.on(event, (...args: any[]) => {
511 512 513 514 515 516 517 518 519 520 521 522 523
        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)
      })
524 525 526 527

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

532
  private addListenerFunction (event: WechatyEventName, listener: AnyFunction): void {
533 534 535 536 537 538 539 540 541 542 543 544
    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)
      }
    })
  }

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

548 549 550 551 552 553 554 555 556 557 558 559
    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 (李卓桓) 已提交
560
    const puppet       = this.options.puppet || config.systemPuppetName()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
561
    const puppetMemory = this.memory.multiplex(PUPPET_MEMORY_NAME)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
562

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
563 564 565 566 567
    const puppetInstance = await PuppetManager.resolve({
      puppet,
      puppetOptions : this.options.puppetOptions,
      wechaty       : this,
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
568

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
569 570 571
    /**
     * Plug the Memory Card to Puppet
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
572 573
    puppetInstance.setMemory(puppetMemory)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
574 575
    this.initPuppetEventBridge(puppetInstance)
    this.initPuppetAccessory(puppetInstance)
576
  }
577

578
  protected initPuppetEventBridge (puppet: Puppet) {
579 580 581
    log.verbose('Wechaty', 'initPuppetEventBridge(%s)', puppet)

    const eventNameList: PuppetEventName[] = Object.keys(PUPPET_EVENT_DICT) as PuppetEventName[]
582 583 584 585
    for (const eventName of eventNameList) {
      log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', eventName)

      switch (eventName) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
586 587 588 589 590 591
        case 'dong':
          puppet.on('dong', data => {
            this.emit('dong', data)
          })
          break

592 593 594 595 596 597
        case 'error':
          puppet.on('error', error => {
            this.emit('error', new Error(error))
          })
          break

598 599 600 601 602
        case 'watchdog':
          puppet.on('watchdog', data => {
            /**
             * Use `watchdog` event from Puppet to `heartbeat` Wechaty.
             */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
603
            // TODO: use a throttle queue to prevent beat too fast.
604 605 606 607
            this.emit('heartbeat', data)
          })
          break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
608 609 610 611 612 613 614 615 616 617
        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)
618 619 620 621 622
          })
          break

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

        case 'logout':
          puppet.on('logout', async contactId => {
631
            const contact = this.ContactSelf.load(contactId)
632 633 634 635 636 637 638 639 640 641 642 643 644
            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

645 646 647 648 649 650 651 652 653
        case 'ready':
          puppet.on('ready', () => {
            log.silly('Wechaty', 'initPuppetEventBridge() puppet.on(ready)')

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

654 655 656
        case 'room-invite':
          puppet.on('room-invite', async roomInvitationId => {
            const roomInvitation = this.RoomInvitation.load(roomInvitationId)
657
            this.emit('room-invite', roomInvitation)
658 659 660
          })
          break

661 662 663 664 665 666 667 668 669 670 671 672
        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)
673
            room.emit('join', inviteeList, inviter)
674 675 676 677
          })
          break

        case 'room-leave':
678
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
679 680 681 682 683 684
            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()))

685
            let remover: undefined | Contact
686 687 688 689 690 691
            if (removerId) {
              remover = this.Contact.load(removerId)
              await remover.ready()
            }

            this.emit('room-leave', room, leaverList, remover)
692
            room.emit('leave', leaverList, remover)
693 694 695 696
          })
          break

        case 'room-topic':
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
697
          puppet.on('room-topic', async (roomId, newTopic, oldTopic, changerId) => {
698 699 700 701 702 703
            const room = this.Room.load(roomId)
            await room.ready()

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

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
704 705
            this.emit('room-topic', room, newTopic, oldTopic, changer)
            room.emit('topic', newTopic, oldTopic, changer)
706 707 708 709
          })
          break

        case 'scan':
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
710 711
          puppet.on('scan', async (qrcode, status, data) => {
            this.emit('scan', qrcode, status, data)
712 713 714
          })
          break

715
        case 'watchdog':
716
        case 'reset':
717 718
          break

719
        default:
720
          throw new Error('eventName ' + eventName + ' unsupported!')
721 722

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
723
    }
724
  }
725

726
  protected initPuppetAccessory (puppet: Puppet) {
727
    log.verbose('Wechaty', 'initAccessory(%s)', puppet)
728

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

739
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
740
     * 2. Set Puppet
741
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
742 743 744 745 746
    this.Contact.puppet        = puppet
    this.ContactSelf.puppet    = puppet
    this.Friendship.puppet     = puppet
    this.Message.puppet        = puppet
    this.Room.puppet           = puppet
747
    this.RoomInvitation.puppet = puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
748

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
749
    this.puppet = puppet
750 751
  }

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

    if (this.state.on()) {
      log.silly('Wechaty', 'start() on a starting/started instance')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
784
      await this.state.ready('on')
785 786 787 788
      log.silly('Wechaty', 'start() state.ready() resolved')
      return
    }

789 790
    this.readyState.off(true)

791 792 793 794
    if (this.lifeTimer) {
      throw new Error('start() lifeTimer exist')
    }

795 796 797
    this.state.on('pending')

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
800
      await this.initPuppet()
801 802
      await this.puppet.start()

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
803 804 805 806 807 808 809 810
      if (this.options.ioToken) {
        this.io = new Io({
          token   : this.options.ioToken,
          wechaty : this,
        })
        await this.io.start()
      }

811
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
812
      console.error(e)
813 814
      log.error('Wechaty', 'start() exception: %s', e && e.message)
      Raven.captureException(e)
815 816 817 818 819 820 821 822 823
      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)
      }
824
      return
825 826 827 828
    }

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

829 830 831 832
    this.lifeTimer = setInterval(() => {
      log.silly('Wechaty', 'start() setInterval() this timer is to keep Wechaty running...')
    }, 1000 * 60 * 60)

833 834 835 836 837 838
    this.state.on(true)
    this.emit('start')

    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
839 840 841 842 843 844 845
  /**
   * Stop the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.stop()
   */
846
  public async stop (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
847 848 849 850
    log.info('Wechaty', '<%s> stop() v%s is stoping ...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
851

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
852
    if (this.state.off()) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
853 854 855
      log.silly('Wechaty', 'stop() on an stopping/stopped instance')
      await this.state.ready('off')
      log.silly('Wechaty', 'stop() state.ready(off) resolved')
856
      return
857
    }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
858

859 860
    this.readyState.off(true)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
861
    this.state.off('pending')
862
    await this.memory.save()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
863

864 865 866 867 868
    if (this.lifeTimer) {
      clearInterval(this.lifeTimer)
      this.lifeTimer = undefined
    }

869
    try {
870
      await this.puppet.stop()
871 872 873
    } catch (e) {
      log.warn('Wechaty', 'stop() puppet.stop() exception: %s', e.message)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
874

875
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
876 877 878 879 880
      if (this.io) {
        await this.io.stop()
        this.io = undefined
      }

881
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
882
      log.error('Wechaty', 'stop() exception: %s', e.message)
883
      Raven.captureException(e)
884
      this.emit('error', e)
885
    }
886 887 888 889

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

890
    return
891
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
892

893 894 895 896 897 898 899
  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 (李卓桓) 已提交
900
  /**
L
lijiarui 已提交
901 902 903 904 905
   * Logout the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.logout()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
906
   */
907
  public async logout (): Promise<void>  {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
908 909
    log.verbose('Wechaty', 'logout()')

910 911 912 913 914 915 916
    try {
      await this.puppet.logout()
    } catch (e) {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      Raven.captureException(e)
      throw e
    }
917
    return
918
  }
919

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
920 921 922 923 924
  /**
   * Get the logon / logoff state
   *
   * @returns {boolean}
   * @example
925
   * if (bot.logonoff()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
926 927 928 929 930
   *   console.log('Bot logined')
   * } else {
   *   console.log('Bot not logined')
   * }
   */
931
  public logonoff (): boolean {
932
    return this.puppet.logonoff()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
933 934
  }

935
  /**
L
lijiarui 已提交
936 937
   * @description
   * Should use {@link Wechaty#userSelf} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
938
   * @deprecated Use `userSelf()` instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
939
   * @private
940
   */
941
  public self (): Contact {
942
    log.warn('Wechaty', 'self() DEPRECATED. use userSelf() instead.')
943 944 945
    return this.userSelf()
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
946
  /**
L
lijiarui 已提交
947 948
   * Get current user
   *
949
   * @returns {ContactSelf}
L
lijiarui 已提交
950
   * @example
951
   * const contact = bot.userSelf()
L
lijiarui 已提交
952
   * console.log(`Bot is ${contact.name()}`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
953
   */
954
  public userSelf (): ContactSelf {
955
    const userId = this.puppet.selfId()
956
    const user = this.ContactSelf.load(userId)
957
    return user
958
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
959

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
960 961 962 963 964
  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>
965

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

1000
  public async say (textOrContactOrFile: string | Contact | FileBox): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    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')
    }
1013 1014
  }

L
lijiarui 已提交
1015 1016 1017
  /**
   * @private
   */
1018
  public static version (forceNpm = false): string {
L
lijiarui 已提交
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
    if (!forceNpm) {
      const revision = config.gitRevision()
      if (revision) {
        return `#git[${revision}]`
      }
    }
    return VERSION
  }

 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1029
  * @private
L
lijiarui 已提交
1030 1031 1032 1033 1034 1035 1036 1037 1038
  * 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'
  */
1039
  public version (forceNpm = false): string {
L
lijiarui 已提交
1040 1041 1042
    return Wechaty.version(forceNpm)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1043
  /**
L
lijiarui 已提交
1044
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1045
   */
1046
  public static async sleep (millisecond: number): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1047
    await new Promise(resolve => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1048 1049 1050 1051
      setTimeout(resolve, millisecond)
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1052
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
1053
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1054
   */
1055
  public ding (data?: string): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1056 1057
    log.silly('Wechaty', 'ding(%s)', data || '')

1058
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1059
      this.puppet.ding(data)
1060 1061 1062
    } catch (e) {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      Raven.captureException(e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1063
      throw e
1064
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1065
  }
1066 1067 1068 1069

  /**
   * @private
   */
1070
  private memoryCheck (minMegabyte = 4): void {
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
    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 (李卓桓) 已提交
1081 1082 1083 1084

  /**
   * @private
   */
1085
  public async reset (reason?: string): Promise<void> {
1086
    log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
1087 1088
    await this.puppet.stop()
    await this.puppet.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1089 1090 1091
    return
  }

1092
  public unref (): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1093 1094 1095 1096 1097 1098 1099
    log.verbose('Wechaty', 'unref()')

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

    this.puppet.unref()
1100
  }
1101
}