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,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
101 102
  name?          : null | string,         // Wechaty Name
  profile?       : null | string,         // DEPRECATED: use name instead
103
  puppet?        : PuppetModuleName | Puppet,   // Puppet name or instance
104 105
  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 readonly 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 243 244
    if (!options.name && options.profile) {
      log.verbose('Wechaty', 'constuctor() WechatyOptions.profile DEPRECATED. use WechatyOptions.name instead.')
      options.name = options.profile
    }

    options.name = options.name === null
                    ? null
                    : (options.name || config.default.DEFAULT_PROFILE)
245

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
246
    this.id = cuid()
247 248 249 250

    this.memory = options.memory
      ? options.memory
      : new MemoryCard(
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
251 252
          options.name
          ? { name: options.name }
253 254
          : undefined,
        )
255 256 257

    this.state      = new StateSwitch('Wechaty', log)
    this.readyState = new StateSwitch('WechatyReady', log)
258 259

    /**
L
lijiarui 已提交
260
     * @ignore
261 262 263 264 265 266 267
     * 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 (李卓桓) 已提交
268 269 270 271 272
    this.Contact        = cloneClass(Contact)
    this.ContactSelf    = cloneClass(ContactSelf)
    this.Friendship     = cloneClass(Friendship)
    this.Message        = cloneClass(Message)
    this.Room           = cloneClass(Room)
273
    this.RoomInvitation = cloneClass(RoomInvitation)
274 275

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
279
  /**
Huan (李卓桓)'s avatar
jsdoc  
Huan (李卓桓) 已提交
280
   * @private
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
281
   */
282
  public toString () {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
283 284 285 286 287
    if (!this.options) {
      return this.constructor.name
    }

    return [
288 289
      'Wechaty#',
      this.id,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
290
      `<${this.options && this.options.puppet || ''}>`,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
291
      `(${this.memory.name || ''})`,
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
292 293
    ].join('')
  }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
294

295 296 297 298 299 300
  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
301
  public emit (event: 'ready')                                                                         : boolean
302
  public emit (event: 'room-invite', roomInvitation: RoomInvitation)                                   : boolean
303 304 305 306
  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
307
  public emit (event: 'start' | 'stop')                                                                : boolean
308 309

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

312
  public emit (
313 314 315 316 317 318
    event:   WechatyEventName,
    ...args: any[]
  ): boolean {
    return super.emit(event, ...args)
  }

319
  public on (event: 'dong'       , listener: string | ((this: Wechaty, data?: string) => void))                                                    : this
320 321 322
  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
323
  public on (event: 'login' | 'logout', listener: string | ((this: Wechaty, user: ContactSelf) => void))                                           : this
324
  public on (event: 'message'    , listener: string | ((this: Wechaty, message: Message) => void))                                                 : this
325
  public on (event: 'ready'      , listener: string | ((this: Wechaty) => void))                                                                   : this
326
  public on (event: 'room-invite', listener: string | ((this: Wechaty, roomInvitation: RoomInvitation) => void))                                   : this
327 328 329 330
  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
331
  public on (event: 'start' | 'stop', listener: string | ((this: Wechaty) => void))                                                                : this
332

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
336
  /**
L
lijiarui 已提交
337 338 339 340 341 342 343 344
   * @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 已提交
345
   * @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 已提交
346 347 348
   * @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 已提交
349
   * @property   {string}  room-invite - Emit when there is a room invitation, see more in  {@link RoomInvitation}
L
lijiarui 已提交
350
   *                                    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 已提交
351 352
   * @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 已提交
353 354 355 356 357 358
   */

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

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

498 499
    // DEPRECATED for 'friend' event
    if (event as any === 'friend') {
500
      log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
501 502 503
      if (typeof listener === 'function') {
        const oldListener = listener
        listener = (...args: any[]) => {
504
          log.warn('Wechaty', `on('friend', contact, friendRequest) is DEPRECATED. use on('friendship', friendship) instead`)
505 506 507 508 509
          oldListener.apply(this, args)
        }
      }
    }

510
    if (typeof listener === 'function') {
511
      this.addListenerFunction(event, listener)
512
    } else {
513
      this.addListenerModuleFile(event, listener)
514
    }
515
    return this
516 517
  }

518
  private addListenerModuleFile (event: WechatyEventName, modulePath: string): void {
519
    const absoluteFilename = callerResolve(modulePath, __filename)
520 521
    log.verbose('Wechaty', 'onModulePath() hotImport(%s)', absoluteFilename)

522
    hotImport(absoluteFilename)
523
      .then((func: AnyFunction) => super.on(event, (...args: any[]) => {
524 525 526 527 528 529 530 531 532 533 534 535 536
        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)
      })
537 538 539 540

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

545
  private addListenerFunction (event: WechatyEventName, listener: AnyFunction): void {
546 547 548 549 550 551 552 553 554 555 556 557
    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)
      }
    })
  }

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

561 562 563 564 565 566 567 568 569 570 571 572
    let inited = false
    try {
      inited = !!this.puppet
    } catch (e) {
      inited = false
    }

    if (inited) {
      log.verbose('Wechaty', 'initPuppet(%s) had already been inited, no need to init twice', this.options.puppet)
      return
    }

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

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
576 577 578 579 580
    const puppetInstance = await PuppetManager.resolve({
      puppet,
      puppetOptions : this.options.puppetOptions,
      wechaty       : this,
    })
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
581

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
582 583 584
    /**
     * Plug the Memory Card to Puppet
     */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
585 586
    puppetInstance.setMemory(puppetMemory)

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
587 588
    this.initPuppetEventBridge(puppetInstance)
    this.initPuppetAccessory(puppetInstance)
589
  }
590

591
  protected initPuppetEventBridge (puppet: Puppet) {
592 593 594
    log.verbose('Wechaty', 'initPuppetEventBridge(%s)', puppet)

    const eventNameList: PuppetEventName[] = Object.keys(PUPPET_EVENT_DICT) as PuppetEventName[]
595 596 597 598
    for (const eventName of eventNameList) {
      log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', eventName)

      switch (eventName) {
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
599 600 601 602 603 604
        case 'dong':
          puppet.on('dong', data => {
            this.emit('dong', data)
          })
          break

605 606 607 608 609 610
        case 'error':
          puppet.on('error', error => {
            this.emit('error', new Error(error))
          })
          break

611 612 613 614 615
        case 'watchdog':
          puppet.on('watchdog', data => {
            /**
             * Use `watchdog` event from Puppet to `heartbeat` Wechaty.
             */
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
616
            // TODO: use a throttle queue to prevent beat too fast.
617 618 619 620
            this.emit('heartbeat', data)
          })
          break

Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
621 622 623 624 625 626 627 628 629 630
        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)
631 632 633 634 635
          })
          break

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

        case 'logout':
          puppet.on('logout', async contactId => {
645
            const contact = this.ContactSelf.load(contactId)
646 647
            await contact.ready()
            this.emit('logout', contact)
648
            await this.memory.save()
649 650 651 652 653 654 655 656 657 658 659
          })
          break

        case 'message':
          puppet.on('message', async messageId => {
            const msg = this.Message.create(messageId)
            await msg.ready()
            this.emit('message', msg)
          })
          break

660 661 662 663 664 665 666 667 668
        case 'ready':
          puppet.on('ready', () => {
            log.silly('Wechaty', 'initPuppetEventBridge() puppet.on(ready)')

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

669 670 671
        case 'room-invite':
          puppet.on('room-invite', async roomInvitationId => {
            const roomInvitation = this.RoomInvitation.load(roomInvitationId)
672
            this.emit('room-invite', roomInvitation)
673 674 675
          })
          break

676 677 678 679 680 681 682 683 684 685 686 687
        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)
688
            room.emit('join', inviteeList, inviter)
689 690 691 692
          })
          break

        case 'room-leave':
693
          puppet.on('room-leave', async (roomId, leaverIdList, removerId) => {
694 695 696 697 698 699
            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()))

700
            let remover: undefined | Contact
701 702 703 704 705 706
            if (removerId) {
              remover = this.Contact.load(removerId)
              await remover.ready()
            }

            this.emit('room-leave', room, leaverList, remover)
707
            room.emit('leave', leaverList, remover)
708 709 710 711
          })
          break

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

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

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

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

730
        case 'watchdog':
731
        case 'reset':
732 733
          break

734
        default:
735
          throw new Error('eventName ' + eventName + ' unsupported!')
736 737

      }
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
738
    }
739
  }
740

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

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

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

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

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

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

804 805
    this.readyState.off(true)

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

810 811 812
    this.state.on('pending')

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

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')
877
    await this.memory.save()
Huan (李卓桓)'s avatar
Huan (李卓桓) 已提交
878

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    this.puppet.unref()
1118
  }
1119
}