wechaty.ts 38.0 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
    if (!options.name && options.profile) {
      log.verbose('Wechaty', 'constuctor() WechatyOptions.profile DEPRECATED. use WechatyOptions.name instead.')
      options.name = options.profile
    }
241
    this.memory = this.options.memory
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
242 243

    this.id = cuid()
244

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

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

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

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

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

284 285 286 287 288 289
  public emit (event: 'dong'       , data?: string)                                                    : boolean
  public emit (event: 'error'      , error: Error)                                                     : boolean
  public emit (event: 'friendship' , friendship: Friendship)                                           : boolean
  public emit (event: 'heartbeat'  , data: any)                                                        : boolean
  public emit (event: 'login' | 'logout', user: ContactSelf)                                           : boolean
  public emit (event: 'message'    , message: Message)                                                 : boolean
290
  public emit (event: 'ready')                                                                         : boolean
291
  public emit (event: 'room-invite', roomInvitation: RoomInvitation)                                   : boolean
292 293 294 295
  public emit (event: 'room-join'  , room: Room, inviteeList : Contact[], inviter  : Contact)          : boolean
  public emit (event: 'room-leave' , room: Room, leaverList  : Contact[], remover? : Contact)          : boolean
  public emit (event: 'room-topic' , room: Room, newTopic: string, oldTopic: string, changer: Contact) : boolean
  public emit (event: 'scan'       , qrcode: string, status: number, data?: string)                    : boolean
296
  public emit (event: 'start' | 'stop')                                                                : boolean
297 298

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

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

308
  public on (event: 'dong'       , listener: string | ((this: Wechaty, data?: string) => void))                                                    : this
309 310 311
  public on (event: 'error'      , listener: string | ((this: Wechaty, error: Error) => void))                                                     : this
  public on (event: 'friendship' , listener: string | ((this: Wechaty, friendship: Friendship) => void))                                           : this
  public on (event: 'heartbeat'  , listener: string | ((this: Wechaty, data: any) => void))                                                        : this
312
  public on (event: 'login' | 'logout', listener: string | ((this: Wechaty, user: ContactSelf) => void))                                           : this
313
  public on (event: 'message'    , listener: string | ((this: Wechaty, message: Message) => void))                                                 : this
314
  public on (event: 'ready'      , listener: string | ((this: Wechaty) => void))                                                                   : this
315
  public on (event: 'room-invite', listener: string | ((this: Wechaty, roomInvitation: RoomInvitation) => void))                                   : this
316 317 318 319
  public on (event: 'room-join'  , listener: string | ((this: Wechaty, room: Room, inviteeList: Contact[],  inviter: Contact) => void))            : this
  public on (event: 'room-leave' , listener: string | ((this: Wechaty, room: Room, leaverList: Contact[], remover?: Contact) => void))             : this
  public on (event: 'room-topic' , listener: string | ((this: Wechaty, room: Room, newTopic: string, oldTopic: string, changer: Contact) => void)) : this
  public on (event: 'scan'       , listener: string | ((this: Wechaty, qrcode: string, status: number, data?: string) => void))                    : this
320
  public on (event: 'start' | 'stop', listener: string | ((this: Wechaty) => void))                                                                : this
321

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

667 668 669
        case 'room-join':
          puppet.on('room-join', async (roomId, inviteeIdList, inviterId) => {
            const room = this.Room.load(roomId)
670
            await room.sync()
671 672 673 674 675 676 677 678

            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)
679
            room.emit('join', inviteeList, inviter)
680 681 682 683
          })
          break

        case 'room-leave':
684
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
685
            const room = this.Room.load(roomId)
686
            await room.sync()
687 688 689 690

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

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

            this.emit('room-leave', room, leaverList, remover)
698
            room.emit('leave', leaverList, remover)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
699 700

            // issue #254
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
701
            if (leaverIdList.includes(this.puppet.selfId())) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
702 703 704 705
              await this.puppet.roomPayloadDirty(roomId)
              await this.puppet.roomMemberPayloadDirty(roomId)
            }

706 707 708 709
          })
          break

        case 'room-topic':
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
710
          puppet.on('room-topic', async (roomId, newTopic, oldTopic, changerId) => {
711
            const room = this.Room.load(roomId)
712
            await room.sync()
713 714 715 716

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

Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
717 718
            this.emit('room-topic', room, newTopic, oldTopic, changer)
            room.emit('topic', newTopic, oldTopic, changer)
719 720 721 722
          })
          break

        case 'scan':
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
723 724
          puppet.on('scan', async (qrcode, status, data) => {
            this.emit('scan', qrcode, status, data)
725 726 727
          })
          break

728
        case 'watchdog':
729
        case 'reset':
730 731
          break

732
        default:
733
          throw new Error('eventName ' + eventName + ' unsupported!')
734 735

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
736
    }
737
  }
738

739
  protected initPuppetAccessory (puppet: Puppet) {
740
    log.verbose('Wechaty', 'initAccessory(%s)', puppet)
741

742
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
743
     * 1. Set Wechaty
744
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
745 746 747 748 749
    this.Contact.wechaty        = this
    this.ContactSelf.wechaty    = this
    this.Friendship.wechaty     = this
    this.Message.wechaty        = this
    this.Room.wechaty           = this
750
    this.RoomInvitation.wechaty = this
751

752
    /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
753
     * 2. Set Puppet
754
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
755 756 757 758 759
    this.Contact.puppet        = puppet
    this.ContactSelf.puppet    = puppet
    this.Friendship.puppet     = puppet
    this.Message.puppet        = puppet
    this.Room.puppet           = puppet
760
    this.RoomInvitation.puppet = puppet
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
761

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
762
    this.puppet = puppet
763 764
  }

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

    if (this.state.on()) {
      log.silly('Wechaty', 'start() on a starting/started instance')
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
797
      await this.state.ready('on')
798 799 800 801
      log.silly('Wechaty', 'start() state.ready() resolved')
      return
    }

802 803
    this.readyState.off(true)

804 805 806 807
    if (this.lifeTimer) {
      throw new Error('start() lifeTimer exist')
    }

808 809 810
    this.state.on('pending')

    try {
811 812 813 814
      if (!this.memory) {
        this.memory = new MemoryCard(this.options.name)
        await this.memory.load()
      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
815
      await this.initPuppet()
816 817
      await this.puppet.start()

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
818 819 820 821 822 823 824 825
      if (this.options.ioToken) {
        this.io = new Io({
          token   : this.options.ioToken,
          wechaty : this,
        })
        await this.io.start()
      }

826
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
827
      console.error(e)
828 829
      log.error('Wechaty', 'start() exception: %s', e && e.message)
      Raven.captureException(e)
830 831 832 833 834 835 836 837 838
      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)
      }
839
      return
840 841 842 843
    }

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

844 845 846 847
    this.lifeTimer = setInterval(() => {
      log.silly('Wechaty', 'start() setInterval() this timer is to keep Wechaty running...')
    }, 1000 * 60 * 60)

848 849 850 851 852 853
    this.state.on(true)
    this.emit('start')

    return
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
854 855 856 857 858 859 860
  /**
   * Stop the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.stop()
   */
861
  public async stop (): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
862 863 864 865
    log.info('Wechaty', '<%s> stop() v%s is stoping ...' ,
                        this.options.puppet || config.systemPuppetName(),
                        this.version(),
            )
866

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
867
    if (this.state.off()) {
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
868 869 870
      log.silly('Wechaty', 'stop() on an stopping/stopped instance')
      await this.state.ready('off')
      log.silly('Wechaty', 'stop() state.ready(off) resolved')
871
      return
872
    }
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
873

874 875
    this.readyState.off(true)

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

878 879 880 881 882
    if (this.lifeTimer) {
      clearInterval(this.lifeTimer)
      this.lifeTimer = undefined
    }

883
    try {
884
      await this.puppet.stop()
885 886 887
    } catch (e) {
      log.warn('Wechaty', 'stop() puppet.stop() exception: %s', e.message)
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
888

889
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
890 891 892 893 894
      if (this.io) {
        await this.io.stop()
        this.io = undefined
      }

895
    } catch (e) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
896
      log.error('Wechaty', 'stop() exception: %s', e.message)
897
      Raven.captureException(e)
898
      this.emit('error', e)
899
    }
900 901 902 903

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

904
    return
905
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
906

907 908 909 910 911 912 913
  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 (李卓桓) 已提交
914
  /**
L
lijiarui 已提交
915 916 917 918 919
   * Logout the bot
   *
   * @returns {Promise<void>}
   * @example
   * await bot.logout()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
920
   */
921
  public async logout (): Promise<void>  {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
922 923
    log.verbose('Wechaty', 'logout()')

924 925 926 927 928 929 930
    try {
      await this.puppet.logout()
    } catch (e) {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      Raven.captureException(e)
      throw e
    }
931
    return
932
  }
933

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
934 935 936 937 938
  /**
   * Get the logon / logoff state
   *
   * @returns {boolean}
   * @example
939
   * if (bot.logonoff()) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
940 941 942 943 944
   *   console.log('Bot logined')
   * } else {
   *   console.log('Bot not logined')
   * }
   */
945
  public logonoff (): boolean {
946
    return this.puppet.logonoff()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
947 948
  }

949
  /**
L
lijiarui 已提交
950 951
   * @description
   * Should use {@link Wechaty#userSelf} instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
952
   * @deprecated Use `userSelf()` instead
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
953
   * @private
954
   */
955
  public self (): Contact {
956
    log.warn('Wechaty', 'self() DEPRECATED. use userSelf() instead.')
957 958 959
    return this.userSelf()
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
960
  /**
L
lijiarui 已提交
961 962
   * Get current user
   *
963
   * @returns {ContactSelf}
L
lijiarui 已提交
964
   * @example
965
   * const contact = bot.userSelf()
L
lijiarui 已提交
966
   * console.log(`Bot is ${contact.name()}`)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
967
   */
968
  public userSelf (): ContactSelf {
969
    const userId = this.puppet.selfId()
970
    const user = this.ContactSelf.load(userId)
971
    return user
972
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
973

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
974 975 976
  public async say (text: string)     : Promise<void>
  public async say (contact: Contact) : Promise<void>
  public async say (file: FileBox)    : Promise<void>
977
  public async say (url: UrlLink)     : Promise<void>
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
978 979

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

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

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

    // Make Typescript Happy:
1019 1020 1021 1022 1023 1024 1025 1026
    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 (李卓桓) 已提交
1027
    } else {
1028
      throw new Error('unsupported: ' + textOrContactOrFileOrUrl)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1029
    }
1030 1031
  }

L
lijiarui 已提交
1032 1033 1034
  /**
   * @private
   */
1035
  public static version (forceNpm = false): string {
L
lijiarui 已提交
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
    if (!forceNpm) {
      const revision = config.gitRevision()
      if (revision) {
        return `#git[${revision}]`
      }
    }
    return VERSION
  }

 /**
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1046
  * @private
L
lijiarui 已提交
1047 1048 1049 1050 1051 1052 1053 1054 1055
  * 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'
  */
1056
  public version (forceNpm = false): string {
L
lijiarui 已提交
1057 1058 1059
    return Wechaty.version(forceNpm)
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1060
  /**
L
lijiarui 已提交
1061
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1062
   */
1063
  public static async sleep (millisecond: number): Promise<void> {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1064
    await new Promise(resolve => {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1065 1066 1067 1068
      setTimeout(resolve, millisecond)
    })
  }

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1069
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
1070
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1071
   */
1072
  public ding (data?: string): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1073 1074
    log.silly('Wechaty', 'ding(%s)', data || '')

1075
    try {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1076
      this.puppet.ding(data)
1077 1078 1079
    } catch (e) {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      Raven.captureException(e)
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1080
      throw e
1081
    }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1082
  }
1083 1084 1085 1086

  /**
   * @private
   */
1087
  private memoryCheck (minMegabyte = 4): void {
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
    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 (李卓桓) 已提交
1098 1099 1100 1101

  /**
   * @private
   */
1102
  public async reset (reason?: string): Promise<void> {
1103
    log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
Huan (李卓桓)'s avatar
wip...  
Huan (李卓桓) 已提交
1104 1105
    await this.puppet.stop()
    await this.puppet.start()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1106 1107 1108
    return
  }

1109
  public unref (): void {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
1110 1111 1112 1113 1114 1115 1116
    log.verbose('Wechaty', 'unref()')

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

    this.puppet.unref()
1117
  }
1118
}