wechaty.ts 35.5 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 91 92
  error     : 'tbw',
  heartbeat : 'tbw',
  start     : 'tbw',
  stop      : 'tbw',
93 94 95
}

export type WechatyEventName  = keyof typeof WECHATY_EVENT_DICT
96 97

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

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

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

131 132
  public readonly state  : StateSwitch

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

139
  private readonly memory : MemoryCard
140

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

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

150
  public readonly Contact       : typeof Contact
151
  public readonly ContactSelf   : typeof ContactSelf
152
  public readonly Friendship    : typeof Friendship
153
  public readonly Message       : typeof Message
154
  public readonly RoomInvitation: typeof RoomInvitation
155
  public readonly Room          : typeof Room
156

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
183
  /**
L
lijiarui 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
   * 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 (李卓桓) 已提交
222
   */
223
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
224
    private options: WechatyOptions = {},
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
225
  ) {
226
    super()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
227 228
    log.verbose('Wechaty', 'contructor()')

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233
    this.id     = cuid()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
234
    this.memory = new MemoryCard(options.profile || undefined)
235
    this.state  = new StateSwitch('Wechaty', log)
236 237

    /**
L
lijiarui 已提交
238
     * @ignore
239 240 241 242 243 244 245
     * 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 (李卓桓) 已提交
246 247 248 249 250
    this.Contact        = cloneClass(Contact)
    this.ContactSelf    = cloneClass(ContactSelf)
    this.Friendship     = cloneClass(Friendship)
    this.Message        = cloneClass(Message)
    this.Room           = cloneClass(Room)
251
    this.RoomInvitation = cloneClass(RoomInvitation)
252 253
  }

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

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

270 271 272 273 274 275
  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
276
  public emit (event: 'room-invite', room: Room, inviter: Contact, roomInvitation: RoomInvitation)    : boolean
277 278 279 280 281
  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
  public emit (event: 'start' | 'stop')                                                                         : boolean
282 283

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

286
  public emit (
287 288 289 290 291 292
    event:   WechatyEventName,
    ...args: any[]
  ): boolean {
    return super.emit(event, ...args)
  }

293
  public on (event: 'dong'       , listener: string | ((this: Wechaty, data?: string) => void))                                                    : this
294 295 296
  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
297
  public on (event: 'login' | 'logout', listener: string | ((this: Wechaty, user: ContactSelf) => void))                                           : this
298
  public on (event: 'message'    , listener: string | ((this: Wechaty, message: Message) => void))                                                 : this
299
  public on (event: 'room-invite', listener: string | ((this: Wechaty, room: Room, inviter: Contact,  roomInvitation: RoomInvitation) => void))    : this
300 301 302 303
  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
304
  public on (event: 'start' | 'stop', listener: string | ((this: Wechaty) => void))                                                                : this
305

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
309
  /**
L
lijiarui 已提交
310 311 312 313 314 315 316 317 318 319 320 321
   * @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>
   *                                    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 已提交
322 323
   * @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 已提交
324 325 326 327 328 329
   */

  /**
   * @desc       Wechaty Class Event Function
   * @typedef    WechatyEventFunction
   * @property   {Function} error           -(this: Wechaty, error: Error) => void callback function
330 331
   * @property   {Function} login           -(this: Wechaty, user: ContactSelf)=> void
   * @property   {Function} logout          -(this: Wechaty, user: ContactSelf) => void
L
lijiarui 已提交
332 333 334 335 336 337 338 339 340 341 342 343
   * @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 (李卓桓) 已提交
344
   * @property   {Function} friendship      -(this: Wechaty, friendship: Friendship) => void
L
lijiarui 已提交
345 346
   * @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 (李卓桓) 已提交
347
   * @property   {Function} room-topic      -(this: Wechaty, room: Room, newTopic: string, oldTopic: string, changer: Contact) => void
L
lijiarui 已提交
348 349 350 351 352
   * @property   {Function} room-leave      -(this: Wechaty, room: Room, leaverList: Contact[]) => void
   */

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

454 455
    // DEPRECATED for 'friend' event
    if (event as any === 'friend') {
456
      log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
457 458 459
      if (typeof listener === 'function') {
        const oldListener = listener
        listener = (...args: any[]) => {
460
          log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
461 462 463 464 465
          oldListener.apply(this, args)
        }
      }
    }

466
    if (typeof listener === 'function') {
467
      this.addListenerFunction(event, listener)
468
    } else {
469
      this.addListenerModuleFile(event, listener)
470
    }
471
    return this
472 473
  }

474
  private addListenerModuleFile (event: WechatyEventName, modulePath: string): void {
475
    const absoluteFilename = callerResolve(modulePath, __filename)
476 477
    log.verbose('Wechaty', 'onModulePath() hotImport(%s)', absoluteFilename)

478
    hotImport(absoluteFilename)
479
      .then((func: AnyFunction) => super.on(event, (...args: any[]) => {
480 481 482 483 484 485 486 487 488 489 490 491 492
        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)
      })
493 494 495 496

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

501
  private addListenerFunction (event: WechatyEventName, listener: AnyFunction): void {
502 503 504 505 506 507 508 509 510 511 512 513
    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)
      }
    })
  }

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

517 518 519 520 521 522 523 524 525 526 527 528
    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 (李卓桓) 已提交
529
    const puppet       = this.options.puppet || config.systemPuppetName()
530
    const puppetMemory = this.memory.sub(puppet.toString())
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
531

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
532 533 534 535 536
    const puppetInstance = await PuppetManager.resolve({
      puppet,
      puppetOptions : this.options.puppetOptions,
      wechaty       : this,
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
537

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
538 539 540
    /**
     * Plug the Memory Card to Puppet
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
541 542
    puppetInstance.setMemory(puppetMemory)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
543 544
    this.initPuppetEventBridge(puppetInstance)
    this.initPuppetAccessory(puppetInstance)
545
  }
546

547
  protected initPuppetEventBridge (puppet: Puppet) {
548
    const eventNameList: PuppetEventName[] = Object.keys(PUPPET_EVENT_DICT) as any
549 550 551 552
    for (const eventName of eventNameList) {
      log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', eventName)

      switch (eventName) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
553 554 555 556 557 558
        case 'dong':
          puppet.on('dong', data => {
            this.emit('dong', data)
          })
          break

559 560 561 562 563 564
        case 'error':
          puppet.on('error', error => {
            this.emit('error', new Error(error))
          })
          break

565 566 567 568 569
        case 'watchdog':
          puppet.on('watchdog', data => {
            /**
             * Use `watchdog` event from Puppet to `heartbeat` Wechaty.
             */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
570
            // TODO: use a throttle queue to prevent beat too fast.
571 572 573 574
            this.emit('heartbeat', data)
          })
          break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
575 576 577 578 579 580 581 582 583 584
        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)
585 586 587 588 589
          })
          break

        case 'login':
          puppet.on('login', async contactId => {
590
            const contact = this.ContactSelf.load(contactId)
591 592 593 594 595 596 597
            await contact.ready()
            this.emit('login', contact)
          })
          break

        case 'logout':
          puppet.on('logout', async contactId => {
598
            const contact = this.ContactSelf.load(contactId)
599 600 601 602 603 604 605 606 607 608 609 610 611
            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

612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
        case 'room-invite':
          puppet.on('room-invite', async roomInvitationId => {
            const roomInvitation = this.RoomInvitation.load(roomInvitationId)

            const room = await roomInvitation.room()
            await room.ready()

            const inviter = await roomInvitation.inviter()
            await inviter.ready()

            this.emit('room-invite', room,  inviter, roomInvitation)
            room.emit('invite',             inviter, roomInvitation)
          })
          break

627 628 629 630 631 632 633 634 635 636 637 638
        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)
639
            room.emit('join', inviteeList, inviter)
640 641 642 643
          })
          break

        case 'room-leave':
644
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
645 646 647 648 649 650
            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()))

651
            let remover: undefined | Contact
652 653 654 655 656 657
            if (removerId) {
              remover = this.Contact.load(removerId)
              await remover.ready()
            }

            this.emit('room-leave', room, leaverList, remover)
658
            room.emit('leave', leaverList, remover)
659 660 661 662
          })
          break

        case 'room-topic':
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
663
          puppet.on('room-topic', async (roomId, newTopic, oldTopic, changerId) => {
664 665 666 667 668 669
            const room = this.Room.load(roomId)
            await room.ready()

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

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
670 671
            this.emit('room-topic', room, newTopic, oldTopic, changer)
            room.emit('topic', newTopic, oldTopic, changer)
672 673 674 675
          })
          break

        case 'scan':
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
676 677
          puppet.on('scan', async (qrcode, status, data) => {
            this.emit('scan', qrcode, status, data)
678 679 680
          })
          break

681
        case 'watchdog':
682
        case 'reset':
683 684
          break

685
        default:
686
          throw new Error('eventName ' + eventName + ' unsupported!')
687 688

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
689
    }
690
  }
691

692
  protected initPuppetAccessory (puppet: Puppet) {
693
    log.verbose('Wechaty', 'initAccessory(%s)', puppet)
694

695
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
696
     * 1. Set Wechaty
697
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
698 699 700 701 702
    this.Contact.wechaty        = this
    this.ContactSelf.wechaty    = this
    this.Friendship.wechaty     = this
    this.Message.wechaty        = this
    this.Room.wechaty           = this
703
    this.RoomInvitation.wechaty = this
704

705
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
706
     * 2. Set Puppet
707
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
708 709 710 711 712
    this.Contact.puppet        = puppet
    this.ContactSelf.puppet    = puppet
    this.Friendship.puppet     = puppet
    this.Message.puppet        = puppet
    this.Room.puppet           = puppet
713
    this.RoomInvitation.puppet = puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
714

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

718
  /**
L
lijiarui 已提交
719 720 721
   * @desc
   * use {@link Wechaty#start} instead
   * @deprecated
722
   */
723
  public async init (): Promise<void> {
724 725 726
    log.warn('Wechaty', 'init() DEPRECATED. use start() instead.')
    return this.start()
  }
727 728 729 730
  /**
   * Start the bot, return Promise.
   *
   * @returns {Promise<void>}
L
lijiarui 已提交
731 732 733
   * @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
734 735 736 737
   * @example
   * await bot.start()
   * // do other stuff with bot here
   */
738
  public async start (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
739 740 741 742
    log.info('Wechaty', '<%s> start() v%s is starting...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
743 744
    log.verbose('Wechaty', 'puppet: %s'   , this.options.puppet)
    log.verbose('Wechaty', 'profile: %s'  , this.options.profile)
745
    log.verbose('Wechaty', 'id: %s'       , this.id)
746 747 748

    if (this.state.on()) {
      log.silly('Wechaty', 'start() on a starting/started instance')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
749
      await this.state.ready('on')
750 751 752 753
      log.silly('Wechaty', 'start() state.ready() resolved')
      return
    }

754 755 756 757
    if (this.lifeTimer) {
      throw new Error('start() lifeTimer exist')
    }

758 759 760
    this.state.on('pending')

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
763
      await this.initPuppet()
764 765
      await this.puppet.start()

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
766 767 768 769 770 771 772 773
      if (this.options.ioToken) {
        this.io = new Io({
          token   : this.options.ioToken,
          wechaty : this,
        })
        await this.io.start()
      }

774
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
775
      console.error(e)
776 777
      log.error('Wechaty', 'start() exception: %s', e && e.message)
      Raven.captureException(e)
778 779 780 781 782 783 784 785 786
      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)
      }
787
      return
788 789 790 791
    }

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

792 793 794 795
    this.lifeTimer = setInterval(() => {
      log.silly('Wechaty', 'start() setInterval() this timer is to keep Wechaty running...')
    }, 1000 * 60 * 60)

796 797 798 799 800 801
    this.state.on(true)
    this.emit('start')

    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
802 803 804 805 806 807 808
  /**
   * Stop the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.stop()
   */
809
  public async stop (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
810 811 812 813
    log.info('Wechaty', '<%s> stop() v%s is stoping ...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
814

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
815
    if (this.state.off()) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
816 817 818
      log.silly('Wechaty', 'stop() on an stopping/stopped instance')
      await this.state.ready('off')
      log.silly('Wechaty', 'stop() state.ready(off) resolved')
819
      return
820
    }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
821

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
822
    this.state.off('pending')
823
    await this.memory.save()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
824

825 826 827 828 829
    if (this.lifeTimer) {
      clearInterval(this.lifeTimer)
      this.lifeTimer = undefined
    }

830
    try {
831
      await this.puppet.stop()
832 833 834
    } catch (e) {
      log.warn('Wechaty', 'stop() puppet.stop() exception: %s', e.message)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
835

836
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
837 838 839 840 841
      if (this.io) {
        await this.io.stop()
        this.io = undefined
      }

842
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
843
      log.error('Wechaty', 'stop() exception: %s', e.message)
844
      Raven.captureException(e)
845
      this.emit('error', e)
846
    }
847 848 849 850 851 852 853 854 855 856

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

    /**
     * MUST use setImmediate at here(the end of this function),
     * because we need to run the micro task registered by the `emit` method
     */
    setImmediate(() => this.puppet.removeAllListeners())

857
    return
858
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
859

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
860
  /**
L
lijiarui 已提交
861 862 863 864 865
   * Logout the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.logout()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
866
   */
867
  public async logout (): Promise<void>  {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
868 869
    log.verbose('Wechaty', 'logout()')

870 871 872 873 874 875 876
    try {
      await this.puppet.logout()
    } catch (e) {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      Raven.captureException(e)
      throw e
    }
877
    return
878
  }
879

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
880 881 882 883 884
  /**
   * Get the logon / logoff state
   *
   * @returns {boolean}
   * @example
885
   * if (bot.logonoff()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
886 887 888 889 890
   *   console.log('Bot logined')
   * } else {
   *   console.log('Bot not logined')
   * }
   */
891
  public logonoff (): boolean {
892
    return this.puppet.logonoff()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
893 894
  }

895
  /**
L
lijiarui 已提交
896 897
   * @description
   * Should use {@link Wechaty#userSelf} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
898
   * @deprecated Use `userSelf()` instead
899
   */
900
  public self (): Contact {
901
    log.warn('Wechaty', 'self() DEPRECATED. use userSelf() instead.')
902 903 904
    return this.userSelf()
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
905
  /**
L
lijiarui 已提交
906 907
   * Get current user
   *
908
   * @returns {ContactSelf}
L
lijiarui 已提交
909
   * @example
910
   * const contact = bot.userSelf()
L
lijiarui 已提交
911
   * console.log(`Bot is ${contact.name()}`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
912
   */
913
  public userSelf (): ContactSelf {
914
    const userId = this.puppet.selfId()
915
    const user = this.ContactSelf.load(userId)
916
    return user
917
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
918

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
919
  /**
L
lijiarui 已提交
920
   * Send message to userSelf, in other words, bot send message to itself.
921 922 923
   * > 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 已提交
924 925 926
   * @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
927
   *
L
lijiarui 已提交
928
   * @returns {Promise<void>}
L
lijiarui 已提交
929
   *
L
lijiarui 已提交
930 931 932 933 934
   * @example
   * const bot = new Wechaty()
   * await bot.start()
   * // after logged in
   *
L
lijiarui 已提交
935
   * // 1. send text to bot itself
L
lijiarui 已提交
936 937
   * await bot.say('hello!')
   *
L
lijiarui 已提交
938
   * // 2. send Contact to bot itself
L
lijiarui 已提交
939 940 941
   * const contact = bot.Contact.load('contactId')
   * await bot.say(contact)
   *
L
lijiarui 已提交
942
   * // 3. send Image to bot itself from remote url
L
lijiarui 已提交
943 944 945 946
   * import { FileBox }  from 'file-box'
   * const fileBox = FileBox.fromUrl('https://chatie.io/wechaty/images/bot-qr-code.png')
   * await bot.say(fileBox)
   *
L
lijiarui 已提交
947
   * // 4. send Image to bot itself from local file
L
lijiarui 已提交
948 949 950
   * import { FileBox }  from 'file-box'
   * const fileBox = FileBox.fromLocal('/tmp/text.jpg')
   * await bot.say(fileBox)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
951
   */
952
  public async say (textOrContactOrFile: string | Contact | FileBox): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
953 954 955 956 957 958 959 960 961 962 963 964
    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')
    }
965 966
  }

L
lijiarui 已提交
967 968 969
  /**
   * @private
   */
970
  public static version (forceNpm = false): string {
L
lijiarui 已提交
971 972 973 974 975 976 977 978 979 980
    if (!forceNpm) {
      const revision = config.gitRevision()
      if (revision) {
        return `#git[${revision}]`
      }
    }
    return VERSION
  }

 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
981
  * @private
L
lijiarui 已提交
982 983 984 985 986 987 988 989 990
  * 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'
  */
991
  public version (forceNpm = false): string {
L
lijiarui 已提交
992 993 994
    return Wechaty.version(forceNpm)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
995
  /**
L
lijiarui 已提交
996
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
997
   */
998
  public static async sleep (millisecond: number): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
999
    await new Promise(resolve => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1000 1001 1002 1003
      setTimeout(resolve, millisecond)
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1004
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
1005
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1006
   */
1007
  public ding (data?: string): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1008 1009
    log.silly('Wechaty', 'ding(%s)', data || '')

1010
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1011
      this.puppet.ding(data)
1012 1013 1014
    } catch (e) {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      Raven.captureException(e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1015
      throw e
1016
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1017
  }
1018 1019 1020 1021

  /**
   * @private
   */
1022
  private memoryCheck (minMegabyte = 4): void {
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
    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 (李卓桓) 已提交
1033 1034 1035 1036

  /**
   * @private
   */
1037
  public async reset (reason?: string): Promise<void> {
1038
    log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
1039 1040
    await this.puppet.stop()
    await this.puppet.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1041 1042 1043
    return
  }

1044
  public unref (): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1045 1046 1047 1048 1049 1050 1051
    log.verbose('Wechaty', 'unref()')

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

    this.puppet.unref()
1052
  }
1053
}