wechaty.ts 37.7 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
  PuppetModuleName,
72
}                       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
  UrlLink,
85
}                       from './user/'
86 87

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

export type WechatyEventName  = keyof typeof WECHATY_EVENT_DICT
98 99

export interface WechatyOptions {
100
  memory?        : MemoryCard,
101 102 103 104 105
  name?          : string,                    // Wechaty Name
  profile?       : null | string,             // DEPRECATED: use name instead
  puppet?        : PuppetModuleName | Puppet, // Puppet name or instance
  puppetOptions? : PuppetOptions,             // Puppet TOKEN
  ioToken?       : string,                    // Io TOKEN
106
}
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
107

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

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

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

137 138
  public  readonly state      : StateSwitch
  private readonly readyState : StateSwitch
139

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

146
  private memory?: MemoryCard
147

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

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

157
  public readonly Contact       : typeof Contact
158
  public readonly ContactSelf   : typeof ContactSelf
159
  public readonly Friendship    : typeof Friendship
160
  public readonly Message       : typeof Message
161
  public readonly RoomInvitation: typeof RoomInvitation
162
  public readonly Room          : typeof Room
163
  public readonly UrlLink       : typeof UrlLink
164

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
191
  /**
L
lijiarui 已提交
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
   * 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`
221
   * @property   {PuppetModuleName | Puppet}    puppet             -Puppet name or instance
L
lijiarui 已提交
222 223 224 225 226 227 228 229
   * @property   {Partial<PuppetOptions>} puppetOptions      -Puppet TOKEN
   * @property   {string}                 ioToken            -Io TOKEN
   */

  /**
   * Creates an instance of Wechaty.
   * @param {WechatyOptions} [options={}]
   *
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
230
   */
231
  constructor (
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
232
    private options: WechatyOptions = {},
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
233
  ) {
234
    super()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
235 236
    log.verbose('Wechaty', 'contructor()')

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
237 238 239 240 241 242
    if (!options.name && options.profile) {
      log.verbose('Wechaty', 'constuctor() WechatyOptions.profile DEPRECATED. use WechatyOptions.name instead.')
      options.name = options.profile
    }

    this.id = cuid()
243

244 245
    this.state      = new StateSwitch('Wechaty', log)
    this.readyState = new StateSwitch('WechatyReady', log)
246 247

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

    // No need to set puppet/wechaty, so no need to clone
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
264
    this.UrlLink = UrlLink
265 266
  }

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

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

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

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

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

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

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
324
  /**
L
lijiarui 已提交
325 326 327 328 329 330 331 332
   * @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.
L
lijiarui 已提交
333
   * @property   {string}  ready      - Emit when all data has load completed, in wechaty-puppet-padchat, it means it has sync Contact and Room completed
L
lijiarui 已提交
334 335 336
   * @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
   * @property   {Function} message         -(this: Wechaty, message: Message) => void
L
lijiarui 已提交
363
   * @property   {Function} ready           -(this: Wechaty) => void
L
lijiarui 已提交
364
   * @property   {Function} room-join       -(this: Wechaty, room: Room, inviteeList: Contact[],  inviter: Contact) => void
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
365
   * @property   {Function} room-topic      -(this: Wechaty, room: Room, newTopic: string, oldTopic: string, changer: Contact) => void
L
lijiarui 已提交
366
   * @property   {Function} room-leave      -(this: Wechaty, room: Room, leaverList: Contact[]) => void
L
lijiarui 已提交
367 368
   * @property   {Function} room-invite     -(this: Wechaty, room: Room, leaverList: Contact[]) => void <br>
   *                                        see more in  {@link RoomInvitation}
L
lijiarui 已提交
369 370 371 372
   */

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

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

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

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

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

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

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

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

549 550 551 552 553 554 555 556 557 558 559 560
    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
    }

561 562 563 564
    if (!this.memory) {
      throw new Error('no memory')
    }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
565
    const puppet       = this.options.puppet || config.systemPuppetName()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
566
    const puppetMemory = this.memory.multiplex(PUPPET_MEMORY_NAME)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
567

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
568 569 570 571 572
    const puppetInstance = await PuppetManager.resolve({
      puppet,
      puppetOptions : this.options.puppetOptions,
      wechaty       : this,
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
573

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
574 575 576
    /**
     * Plug the Memory Card to Puppet
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
577 578
    puppetInstance.setMemory(puppetMemory)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
579 580
    this.initPuppetEventBridge(puppetInstance)
    this.initPuppetAccessory(puppetInstance)
581
  }
582

583
  protected initPuppetEventBridge (puppet: Puppet) {
584 585 586
    log.verbose('Wechaty', 'initPuppetEventBridge(%s)', puppet)

    const eventNameList: PuppetEventName[] = Object.keys(PUPPET_EVENT_DICT) as PuppetEventName[]
587 588 589 590
    for (const eventName of eventNameList) {
      log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', eventName)

      switch (eventName) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
591 592 593 594 595 596
        case 'dong':
          puppet.on('dong', data => {
            this.emit('dong', data)
          })
          break

597 598 599 600 601 602
        case 'error':
          puppet.on('error', error => {
            this.emit('error', new Error(error))
          })
          break

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
613 614 615 616 617 618 619 620 621 622
        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)
623 624 625 626 627
          })
          break

        case 'login':
          puppet.on('login', async contactId => {
628
            const contact = this.ContactSelf.load(contactId)
629 630 631 632 633 634 635
            await contact.ready()
            this.emit('login', contact)
          })
          break

        case 'logout':
          puppet.on('logout', async contactId => {
636
            const contact = this.ContactSelf.load(contactId)
637 638 639 640 641 642 643
            await contact.ready()
            this.emit('logout', contact)
          })
          break

        case 'message':
          puppet.on('message', async messageId => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
644
            const msg = this.Message.load(messageId)
645 646 647 648 649
            await msg.ready()
            this.emit('message', msg)
          })
          break

650 651 652 653 654 655 656 657 658
        case 'ready':
          puppet.on('ready', () => {
            log.silly('Wechaty', 'initPuppetEventBridge() puppet.on(ready)')

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

659 660 661
        case 'room-invite':
          puppet.on('room-invite', async roomInvitationId => {
            const roomInvitation = this.RoomInvitation.load(roomInvitationId)
662
            this.emit('room-invite', roomInvitation)
663 664 665
          })
          break

666 667 668 669 670 671 672 673 674 675 676 677
        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)
678
            room.emit('join', inviteeList, inviter)
679 680 681 682
          })
          break

        case 'room-leave':
683
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
684 685 686 687 688 689
            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()))

690
            let remover: undefined | Contact
691 692 693 694 695 696
            if (removerId) {
              remover = this.Contact.load(removerId)
              await remover.ready()
            }

            this.emit('room-leave', room, leaverList, remover)
697
            room.emit('leave', leaverList, remover)
698 699 700 701
          })
          break

        case 'room-topic':
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
702
          puppet.on('room-topic', async (roomId, newTopic, oldTopic, changerId) => {
703 704 705 706 707 708
            const room = this.Room.load(roomId)
            await room.ready()

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

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
709 710
            this.emit('room-topic', room, newTopic, oldTopic, changer)
            room.emit('topic', newTopic, oldTopic, changer)
711 712 713 714
          })
          break

        case 'scan':
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
715 716
          puppet.on('scan', async (qrcode, status, data) => {
            this.emit('scan', qrcode, status, data)
717 718 719
          })
          break

720
        case 'watchdog':
721
        case 'reset':
722 723
          break

724
        default:
725
          throw new Error('eventName ' + eventName + ' unsupported!')
726 727

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
728
    }
729
  }
730

731
  protected initPuppetAccessory (puppet: Puppet) {
732
    log.verbose('Wechaty', 'initAccessory(%s)', puppet)
733

734
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
735
     * 1. Set Wechaty
736
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
737 738 739 740 741
    this.Contact.wechaty        = this
    this.ContactSelf.wechaty    = this
    this.Friendship.wechaty     = this
    this.Message.wechaty        = this
    this.Room.wechaty           = this
742
    this.RoomInvitation.wechaty = this
743

744
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
745
     * 2. Set Puppet
746
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
747 748 749 750 751
    this.Contact.puppet        = puppet
    this.ContactSelf.puppet    = puppet
    this.Friendship.puppet     = puppet
    this.Message.puppet        = puppet
    this.Room.puppet           = puppet
752
    this.RoomInvitation.puppet = puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
753

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
754
    this.puppet = puppet
755 756
  }

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

    if (this.state.on()) {
      log.silly('Wechaty', 'start() on a starting/started instance')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
789
      await this.state.ready('on')
790 791 792 793
      log.silly('Wechaty', 'start() state.ready() resolved')
      return
    }

794 795
    this.readyState.off(true)

796 797 798 799
    if (this.lifeTimer) {
      throw new Error('start() lifeTimer exist')
    }

800 801 802
    this.state.on('pending')

    try {
803 804 805 806
      if (!this.memory) {
        this.memory = new MemoryCard(this.options.name)
        await this.memory.load()
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
807
      await this.initPuppet()
808 809
      await this.puppet.start()

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
810 811 812 813 814 815 816 817
      if (this.options.ioToken) {
        this.io = new Io({
          token   : this.options.ioToken,
          wechaty : this,
        })
        await this.io.start()
      }

818
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
819
      console.error(e)
820 821
      log.error('Wechaty', 'start() exception: %s', e && e.message)
      Raven.captureException(e)
822 823 824 825 826 827 828 829 830
      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)
      }
831
      return
832 833 834 835
    }

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

836 837 838 839
    this.lifeTimer = setInterval(() => {
      log.silly('Wechaty', 'start() setInterval() this timer is to keep Wechaty running...')
    }, 1000 * 60 * 60)

840 841 842 843 844 845
    this.state.on(true)
    this.emit('start')

    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
846 847 848 849 850 851 852
  /**
   * Stop the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.stop()
   */
853
  public async stop (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
854 855 856 857
    log.info('Wechaty', '<%s> stop() v%s is stoping ...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
858

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
859
    if (this.state.off()) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
860 861 862
      log.silly('Wechaty', 'stop() on an stopping/stopped instance')
      await this.state.ready('off')
      log.silly('Wechaty', 'stop() state.ready(off) resolved')
863
      return
864
    }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
865

866 867
    this.readyState.off(true)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
868
    this.state.off('pending')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
869

870 871 872 873 874
    if (this.lifeTimer) {
      clearInterval(this.lifeTimer)
      this.lifeTimer = undefined
    }

875
    try {
876
      await this.puppet.stop()
877 878 879
    } catch (e) {
      log.warn('Wechaty', 'stop() puppet.stop() exception: %s', e.message)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
880

881
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
882 883 884 885 886
      if (this.io) {
        await this.io.stop()
        this.io = undefined
      }

887
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
888
      log.error('Wechaty', 'stop() exception: %s', e.message)
889
      Raven.captureException(e)
890
      this.emit('error', e)
891
    }
892 893 894 895

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

896
    return
897
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
898

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

916 917 918 919 920 921 922
    try {
      await this.puppet.logout()
    } catch (e) {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      Raven.captureException(e)
      throw e
    }
923
    return
924
  }
925

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

941
  /**
L
lijiarui 已提交
942 943
   * @description
   * Should use {@link Wechaty#userSelf} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
944
   * @deprecated Use `userSelf()` instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
945
   * @private
946
   */
947
  public self (): Contact {
948
    log.warn('Wechaty', 'self() DEPRECATED. use userSelf() instead.')
949 950 951
    return this.userSelf()
  }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
966 967 968
  public async say (text: string)     : Promise<void>
  public async say (contact: Contact) : Promise<void>
  public async say (file: FileBox)    : Promise<void>
969
  public async say (url: UrlLink)     : Promise<void>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
970 971

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
973
  /**
L
lijiarui 已提交
974
   * Send message to userSelf, in other words, bot send message to itself.
975 976 977
   * > Tips:
   * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/Chatie/wechaty/wiki/Puppet#3-puppet-compatible-table)
   *
978
   * @param {(string | Contact | FileBox)} textOrContactOrFileOrUrl
L
lijiarui 已提交
979 980
   * send text, Contact, or file to bot. </br>
   * You can use {@link https://www.npmjs.com/package/file-box|FileBox} to send file
981
   *
L
lijiarui 已提交
982
   * @returns {Promise<void>}
L
lijiarui 已提交
983
   *
L
lijiarui 已提交
984 985 986 987 988
   * @example
   * const bot = new Wechaty()
   * await bot.start()
   * // after logged in
   *
L
lijiarui 已提交
989
   * // 1. send text to bot itself
L
lijiarui 已提交
990 991
   * await bot.say('hello!')
   *
L
lijiarui 已提交
992
   * // 2. send Contact to bot itself
L
lijiarui 已提交
993 994 995
   * const contact = bot.Contact.load('contactId')
   * await bot.say(contact)
   *
L
lijiarui 已提交
996
   * // 3. send Image to bot itself from remote url
L
lijiarui 已提交
997 998 999 1000
   * import { FileBox }  from 'file-box'
   * const fileBox = FileBox.fromUrl('https://chatie.io/wechaty/images/bot-qr-code.png')
   * await bot.say(fileBox)
   *
L
lijiarui 已提交
1001
   * // 4. send Image to bot itself from local file
L
lijiarui 已提交
1002
   * import { FileBox }  from 'file-box'
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1003
   * const fileBox = FileBox.fromFile('/tmp/text.jpg')
L
lijiarui 已提交
1004
   * await bot.say(fileBox)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1005
   */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1006

1007 1008
  public async say (textOrContactOrFileOrUrl: string | Contact | FileBox | UrlLink): Promise<void> {
    log.verbose('Wechaty', 'say(%s)', textOrContactOrFileOrUrl)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1009 1010

    // Make Typescript Happy:
1011 1012 1013 1014 1015 1016 1017 1018
    if (typeof textOrContactOrFileOrUrl === 'string') {
      await this.userSelf().say(textOrContactOrFileOrUrl)
    } else if (textOrContactOrFileOrUrl instanceof Contact) {
      await this.userSelf().say(textOrContactOrFileOrUrl)
    } else if (textOrContactOrFileOrUrl instanceof FileBox) {
      await this.userSelf().say(textOrContactOrFileOrUrl)
    } else if (textOrContactOrFileOrUrl instanceof UrlLink) {
      await this.userSelf().say(textOrContactOrFileOrUrl)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1019
    } else {
1020
      throw new Error('unsupported: ' + textOrContactOrFileOrUrl)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1021
    }
1022 1023
  }

L
lijiarui 已提交
1024 1025 1026
  /**
   * @private
   */
1027
  public static version (forceNpm = false): string {
L
lijiarui 已提交
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
    if (!forceNpm) {
      const revision = config.gitRevision()
      if (revision) {
        return `#git[${revision}]`
      }
    }
    return VERSION
  }

 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1038
  * @private
L
lijiarui 已提交
1039 1040 1041 1042 1043 1044 1045 1046 1047
  * 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'
  */
1048
  public version (forceNpm = false): string {
L
lijiarui 已提交
1049 1050 1051
    return Wechaty.version(forceNpm)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1052
  /**
L
lijiarui 已提交
1053
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1054
   */
1055
  public static async sleep (millisecond: number): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1056
    await new Promise(resolve => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1057 1058 1059 1060
      setTimeout(resolve, millisecond)
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1061
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
1062
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1063
   */
1064
  public ding (data?: string): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1065 1066
    log.silly('Wechaty', 'ding(%s)', data || '')

1067
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1068
      this.puppet.ding(data)
1069 1070 1071
    } catch (e) {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      Raven.captureException(e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1072
      throw e
1073
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1074
  }
1075 1076 1077 1078

  /**
   * @private
   */
1079
  private memoryCheck (minMegabyte = 4): void {
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
    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 (李卓桓) 已提交
1090 1091 1092 1093

  /**
   * @private
   */
1094
  public async reset (reason?: string): Promise<void> {
1095
    log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
1096 1097
    await this.puppet.stop()
    await this.puppet.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1098 1099 1100
    return
  }

1101
  public unref (): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1102 1103 1104 1105 1106 1107 1108
    log.verbose('Wechaty', 'unref()')

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

    this.puppet.unref()
1109
  }
1110
}