UNPKG

30.1 kBPlain TextView Raw
1/**
2 * Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
3 *
4 * @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
5 * Wechaty Contributors <https://github.com/wechaty>.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 */
20import cuid from 'cuid'
21import os from 'os'
22
23import { StateSwitch } from 'state-switch'
24import { instanceToClass } from 'clone-class'
25import {
26 Puppet,
27
28 MemoryCard,
29
30 PUPPET_EVENT_DICT,
31 PuppetEventName,
32 PuppetOptions,
33} from 'wechaty-puppet'
34
35import {
36 FileBox,
37 Raven,
38
39 config,
40 log,
41} from './config'
42
43import {
44 VERSION,
45 GIT_COMMIT_HASH,
46} from './version'
47
48import {
49 Sayable,
50} from './types'
51
52import {
53 Io,
54} from './io'
55import {
56 PuppetModuleName,
57} from './puppet-config'
58import {
59 PuppetManager,
60} from './puppet-manager'
61
62import {
63 Contact,
64 ContactSelf,
65 Friendship,
66 Image,
67 Message,
68 MiniProgram,
69 Room,
70 RoomInvitation,
71 Tag,
72 UrlLink,
73
74 wechatifyContact,
75 wechatifyContactSelf,
76 wechatifyFriendship,
77 wechatifyImage,
78 wechatifyMessage,
79 wechatifyMiniProgram,
80 wechatifyRoom,
81 wechatifyRoomInvitation,
82 wechatifyTag,
83 wechatifyUrlLink,
84} from './user/mod'
85
86import { timestampToDate } from './helper-functions/pure/timestamp-to-date'
87
88import {
89 WechatyEventEmitter,
90 WechatyEventName,
91} from './events/wechaty-events'
92
93import {
94 WechatyPlugin,
95 WechatyPluginUninstaller,
96 isWechatyPluginUninstaller,
97} from './plugin'
98
99export interface WechatyOptions {
100 memory? : MemoryCard,
101 name? : string, // Wechaty Name
102
103 // @deprecated: use `name` instead of `profile`
104 profile? : null | string, // DEPRECATED: use name instead
105
106 puppet? : PuppetModuleName | Puppet, // Puppet name or instance
107 puppetOptions? : PuppetOptions, // Puppet TOKEN
108 ioToken? : string, // Io TOKEN
109}
110
111const PUPPET_MEMORY_NAME = 'puppet'
112
113/**
114 * Main bot class.
115 *
116 * A `Bot` is a WeChat client depends on which puppet you use.
117 * It may equals
118 * - web-WeChat, when you use: [puppet-puppeteer](https://github.com/wechaty/wechaty-puppet-puppeteer)/[puppet-wechat4u](https://github.com/wechaty/wechaty-puppet-wechat4u)
119 * - ipad-WeChat, when you use: [puppet-padchat](https://github.com/wechaty/wechaty-puppet-padchat)
120 * - ios-WeChat, when you use: puppet-ioscat
121 *
122 * See more:
123 * - [What is a Puppet in Wechaty](https://github.com/wechaty/wechaty-getting-started/wiki/FAQ-EN#31-what-is-a-puppet-in-wechaty)
124 *
125 * > If you want to know how to send message, see [Message](#Message) <br>
126 * > If you want to know how to get contact, see [Contact](#Contact)
127 *
128 * @example <caption>The World's Shortest ChatBot Code: 6 lines of JavaScript</caption>
129 * const { Wechaty } = require('wechaty')
130 * const bot = new Wechaty()
131 * bot.on('scan', (qrCode, status) => console.log('https://wechaty.github.io/qrcode/' + encodeURIComponent(qrcode)))
132 * bot.on('login', user => console.log(`User ${user} logged in`))
133 * bot.on('message', message => console.log(`Message: ${message}`))
134 * bot.start()
135 */
136export class Wechaty extends WechatyEventEmitter implements Sayable {
137
138 public static readonly VERSION = VERSION
139
140 public readonly state : StateSwitch
141 private readonly readyState : StateSwitch
142 public readonly wechaty : Wechaty
143
144 /**
145 * singleton globalInstance
146 * @ignore
147 */
148 private static globalInstance: Wechaty
149
150 private static globalPluginList: WechatyPlugin[] = []
151
152 private pluginUninstallerList: WechatyPluginUninstaller[]
153
154 private memory?: MemoryCard
155
156 private lifeTimer? : NodeJS.Timer
157 private io? : Io
158
159 public puppet!: Puppet
160
161 /**
162 * the cuid
163 * @ignore
164 */
165 public readonly id : string
166
167 protected wechatifiedContact? : typeof Contact
168 protected wechatifiedContactSelf? : typeof ContactSelf
169 protected wechatifiedFriendship? : typeof Friendship
170 protected wechatifiedImage? : typeof Image
171 protected wechatifiedMessage? : typeof Message
172 protected wechatifiedMiniProgram? : typeof MiniProgram
173 protected wechatifiedRoom? : typeof Room
174 protected wechatifiedRoomInvitation? : typeof RoomInvitation
175 protected wechatifiedTag? : typeof Tag
176 protected wechatifiedUrlLink? : typeof UrlLink
177
178 public get Contact () : typeof Contact { return this.wechatifiedContact! }
179 public get ContactSelf () : typeof ContactSelf { return this.wechatifiedContactSelf! }
180 public get Friendship () : typeof Friendship { return this.wechatifiedFriendship! }
181 public get Image () : typeof Image { return this.wechatifiedImage! }
182 public get Message () : typeof Message { return this.wechatifiedMessage! }
183 public get MiniProgram () : typeof MiniProgram { return this.wechatifiedMiniProgram! }
184 public get Room () : typeof Room { return this.wechatifiedRoom! }
185 public get RoomInvitation () : typeof RoomInvitation { return this.wechatifiedRoomInvitation! }
186 public get Tag () : typeof Tag { return this.wechatifiedTag! }
187 public get UrlLink () : typeof UrlLink { return this.wechatifiedUrlLink! }
188
189 /**
190 * Get the global instance of Wechaty
191 *
192 * @param {WechatyOptions} [options={}]
193 *
194 * @example <caption>The World's Shortest ChatBot Code: 6 lines of JavaScript</caption>
195 * const { Wechaty } = require('wechaty')
196 *
197 * Wechaty.instance() // Global instance
198 * .on('scan', (url, status) => console.log(`Scan QR Code to login: ${status}\n${url}`))
199 * .on('login', user => console.log(`User ${user} logged in`))
200 * .on('message', message => console.log(`Message: ${message}`))
201 * .start()
202 */
203 public static instance (
204 options?: WechatyOptions,
205 ) {
206 if (options && this.globalInstance) {
207 throw new Error('instance can be only initialized once by options!')
208 }
209 if (!this.globalInstance) {
210 this.globalInstance = new Wechaty(options)
211 }
212 return this.globalInstance
213 }
214
215 /**
216 * @param {WechatyPlugin[]} plugins - The plugins you want to use
217 *
218 * @return {Wechaty} - this for chaining,
219 *
220 * @desc
221 * For wechaty ecosystem, allow user to define a 3rd party plugin for the all wechaty instances
222 *
223 * @example
224 * // Report all chat message to my server.
225 *
226 * function WechatyReportPlugin(options: { url: string }) {
227 * return function (this: Wechaty) {
228 * this.on('message', message => http.post(options.url, { data: message }))
229 * }
230 * }
231 *
232 * bot.use(WechatyReportPlugin({ url: 'http://somewhere.to.report.your.data.com' })
233 */
234 public static use (
235 ...plugins: (WechatyPlugin | WechatyPlugin[])[]
236 ) {
237 const pluginList = plugins.flat()
238 this.globalPluginList = this.globalPluginList.concat(pluginList)
239 }
240
241 /**
242 * The term [Puppet](https://github.com/wechaty/wechaty/wiki/Puppet) in Wechaty is an Abstract Class for implementing protocol plugins.
243 * The plugins are the component that helps Wechaty to control the WeChat(that's the reason we call it puppet).
244 * The plugins are named XXXPuppet, for example:
245 * - [PuppetPuppeteer](https://github.com/wechaty/wechaty-puppet-puppeteer):
246 * - [PuppetPadchat](https://github.com/wechaty/wechaty-puppet-padchat)
247 *
248 * @typedef PuppetModuleName
249 * @property {string} PUPPET_DEFAULT
250 * The default puppet.
251 * @property {string} wechaty-puppet-wechat4u
252 * 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.
253 * @property {string} wechaty-puppet-padchat
254 * - Using the WebSocket protocol to connect with a Protocol Server for controlling the iPad WeChat program.
255 * @property {string} wechaty-puppet-puppeteer
256 * - Using the [google puppeteer](https://github.com/GoogleChrome/puppeteer) to control the [WeChat Web API](https://wx.qq.com/) via a chrome browser.
257 * @property {string} wechaty-puppet-mock
258 * - Using the mock data to mock wechat operation, just for test.
259 */
260
261 /**
262 * The option parameter to create a wechaty instance
263 *
264 * @typedef WechatyOptions
265 * @property {string} name -Wechaty Name. </br>
266 * When you set this: </br>
267 * `new Wechaty({name: 'wechaty-name'}) ` </br>
268 * it will generate a file called `wechaty-name.memory-card.json`. </br>
269 * This file stores the login information for bot. </br>
270 * If the file is valid, the bot can auto login so you don't need to scan the qrCode to login again. </br>
271 * Also, you can set the environment variable for `WECHATY_NAME` to set this value when you start. </br>
272 * eg: `WECHATY_NAME="your-cute-bot-name" node bot.js`
273 * @property {PuppetModuleName | Puppet} puppet -Puppet name or instance
274 * @property {Partial<PuppetOptions>} puppetOptions -Puppet TOKEN
275 * @property {string} ioToken -Io TOKEN
276 */
277
278 /**
279 * Creates an instance of Wechaty.
280 * @param {WechatyOptions} [options={}]
281 *
282 */
283 constructor (
284 private options: WechatyOptions = {},
285 ) {
286 super()
287 log.verbose('Wechaty', 'constructor()')
288
289 if (!options.name && options.profile) {
290 log.verbose('Wechaty', 'constructor() WechatyOptions.profile DEPRECATED. use WechatyOptions.name instead.')
291 options.name = options.profile
292 }
293 this.memory = this.options.memory
294
295 this.id = cuid()
296
297 this.state = new StateSwitch('Wechaty', { log })
298 this.readyState = new StateSwitch('WechatyReady', { log })
299
300 this.wechaty = this
301
302 /**
303 * Huan(202008):
304 *
305 * Set max listeners to 1K, so that we can add lots of listeners without the warning message.
306 * The listeners might be one of the following functionilities:
307 * 1. Plugins
308 * 2. Redux Observables
309 * 3. etc...
310 */
311 super.setMaxListeners(1024)
312
313 this.pluginUninstallerList = []
314 this.installGlobalPlugin()
315 }
316
317 /**
318 * @ignore
319 */
320 public toString () {
321 if (!this.options) {
322 return this.constructor.name
323 }
324
325 return [
326 'Wechaty#',
327 this.id,
328 `<${(this.options && this.options.puppet) || ''}>`,
329 `(${(this.memory && this.memory.name) || ''})`,
330 ].join('')
331 }
332
333 /**
334 * Wechaty bot name set by `options.name`
335 * default: `wechaty`
336 */
337 public name () {
338 return this.options.name || 'wechaty'
339 }
340
341 public on (event: WechatyEventName, listener: (...args: any[]) => any): this {
342 log.verbose('Wechaty', 'on(%s, listener) registering... listenerCount: %s',
343 event,
344 this.listenerCount(event),
345 )
346
347 return super.on(event, listener)
348 }
349
350 /**
351 * @param {WechatyPlugin[]} plugins - The plugins you want to use
352 *
353 * @return {Wechaty} - this for chaining,
354 *
355 * @desc
356 * For wechaty ecosystem, allow user to define a 3rd party plugin for the current wechaty instance.
357 *
358 * @example
359 * // The same usage with Wechaty.use().
360 *
361 */
362 public use (...plugins: (WechatyPlugin | WechatyPlugin[])[]) {
363 const pluginList = plugins.flat() as WechatyPlugin[]
364 const uninstallerList = pluginList
365 .map(plugin => plugin(this))
366 .filter(isWechatyPluginUninstaller)
367
368 this.pluginUninstallerList.push(
369 ...uninstallerList,
370 )
371 return this
372 }
373
374 private installGlobalPlugin () {
375
376 const uninstallerList = instanceToClass(this, Wechaty)
377 .globalPluginList
378 .map(plugin => plugin(this))
379 .filter(isWechatyPluginUninstaller)
380
381 this.pluginUninstallerList.push(
382 ...uninstallerList,
383 )
384 }
385
386 private async initPuppet (): Promise<void> {
387 log.verbose('Wechaty', 'initPuppet() %s', this.options.puppet || '')
388
389 const initialized = !!this.puppet
390
391 if (initialized) {
392 log.verbose('Wechaty', 'initPuppet(%s) had already been initialized, no need to init twice', this.options.puppet)
393 return
394 }
395
396 if (!this.memory) {
397 throw new Error('no memory')
398 }
399
400 const puppet = this.options.puppet || config.systemPuppetName()
401 const puppetMemory = this.memory.multiplex(PUPPET_MEMORY_NAME)
402
403 const puppetInstance = await PuppetManager.resolve({
404 puppet,
405 puppetOptions : this.options.puppetOptions,
406 // wechaty : this,
407 })
408
409 /**
410 * Plug the Memory Card to Puppet
411 */
412 puppetInstance.setMemory(puppetMemory)
413
414 this.initPuppetEventBridge(puppetInstance)
415 this.wechatifyUserModules(puppetInstance)
416
417 /**
418 * Private Event
419 * emit puppet when set
420 *
421 * Huan(202005)
422 */
423 ;(this.emit as any)('puppet', puppetInstance)
424 }
425
426 protected initPuppetEventBridge (puppet: Puppet) {
427 log.verbose('Wechaty', 'initPuppetEventBridge(%s)', puppet)
428
429 const eventNameList: PuppetEventName[] = Object.keys(PUPPET_EVENT_DICT) as PuppetEventName[]
430 for (const eventName of eventNameList) {
431 log.verbose('Wechaty',
432 'initPuppetEventBridge() puppet.on(%s) (listenerCount:%s) registering...',
433 eventName,
434 puppet.listenerCount(eventName),
435 )
436
437 switch (eventName) {
438 case 'dong':
439 puppet.on('dong', payload => {
440 this.emit('dong', payload.data)
441 })
442 break
443
444 case 'error':
445 puppet.on('error', payload => {
446 this.emit('error', new Error(payload.data))
447 })
448 break
449
450 case 'heartbeat':
451 puppet.on('heartbeat', payload => {
452 /**
453 * Use `watchdog` event from Puppet to `heartbeat` Wechaty.
454 */
455 // TODO: use a throttle queue to prevent beat too fast.
456 this.emit('heartbeat', payload.data)
457 })
458 break
459
460 case 'friendship':
461 puppet.on('friendship', async payload => {
462 const friendship = this.Friendship.load(payload.friendshipId)
463 await friendship.ready()
464 this.emit('friendship', friendship)
465 friendship.contact().emit('friendship', friendship)
466 })
467 break
468
469 case 'login':
470 puppet.on('login', async payload => {
471 const contact = this.ContactSelf.load(payload.contactId)
472 await contact.ready()
473 this.emit('login', contact)
474 })
475 break
476
477 case 'logout':
478 puppet.on('logout', async payload => {
479 const contact = this.ContactSelf.load(payload.contactId)
480 await contact.ready()
481 this.emit('logout', contact, payload.data)
482 })
483 break
484
485 case 'message':
486 puppet.on('message', async payload => {
487 const msg = this.Message.load(payload.messageId)
488 await msg.ready()
489 this.emit('message', msg)
490
491 const room = msg.room()
492 if (room) {
493 room.emit('message', msg)
494 } else {
495 this.userSelf().emit('message', msg)
496 }
497 })
498 break
499
500 case 'ready':
501 puppet.on('ready', () => {
502 log.silly('Wechaty', 'initPuppetEventBridge() puppet.on(ready)')
503
504 this.emit('ready')
505 this.readyState.on(true)
506 })
507 break
508
509 case 'room-invite':
510 puppet.on('room-invite', async payload => {
511 const roomInvitation = this.RoomInvitation.load(payload.roomInvitationId)
512 this.emit('room-invite', roomInvitation)
513 })
514 break
515
516 case 'room-join':
517 puppet.on('room-join', async payload => {
518 const room = this.Room.load(payload.roomId)
519 await room.sync()
520
521 const inviteeList = payload.inviteeIdList.map(id => this.Contact.load(id))
522 await Promise.all(inviteeList.map(c => c.ready()))
523
524 const inviter = this.Contact.load(payload.inviterId)
525 await inviter.ready()
526 const date = timestampToDate(payload.timestamp)
527
528 this.emit('room-join', room, inviteeList, inviter, date)
529 room.emit('join', inviteeList, inviter, date)
530 })
531 break
532
533 case 'room-leave':
534 puppet.on('room-leave', async payload => {
535 const room = this.Room.load(payload.roomId)
536
537 /**
538 * See: https://github.com/wechaty/wechaty/pull/1833
539 */
540 await room.sync()
541
542 const leaverList = payload.removeeIdList.map(id => this.Contact.load(id))
543 await Promise.all(leaverList.map(c => c.ready()))
544
545 const remover = this.Contact.load(payload.removerId)
546 await remover.ready()
547 const date = timestampToDate(payload.timestamp)
548
549 this.emit('room-leave', room, leaverList, remover, date)
550 room.emit('leave', leaverList, remover, date)
551
552 // issue #254
553 const selfId = this.puppet.selfId()
554 if (selfId && payload.removeeIdList.includes(selfId)) {
555 await this.puppet.roomPayloadDirty(payload.roomId)
556 await this.puppet.roomMemberPayloadDirty(payload.roomId)
557 }
558
559 })
560 break
561
562 case 'room-topic':
563 puppet.on('room-topic', async payload => {
564 const room = this.Room.load(payload.roomId)
565 await room.sync()
566
567 const changer = this.Contact.load(payload.changerId)
568 await changer.ready()
569 const date = timestampToDate(payload.timestamp)
570
571 this.emit('room-topic', room, payload.newTopic, payload.oldTopic, changer, date)
572 room.emit('topic', payload.newTopic, payload.oldTopic, changer, date)
573 })
574 break
575
576 case 'scan':
577 puppet.on('scan', async payload => {
578 this.emit('scan', payload.qrcode || '', payload.status, payload.data)
579 })
580 break
581
582 case 'reset':
583 // Do not propagation `reset` event from puppet
584 break
585
586 default:
587 /**
588 * Check: The eventName here should have the type `never`
589 */
590 throw new Error('eventName ' + eventName + ' unsupported!')
591
592 }
593 }
594 }
595
596 protected wechatifyUserModules (puppet: Puppet) {
597 log.verbose('Wechaty', 'wechatifyUserModules(%s)', puppet)
598
599 if (this.wechatifiedContactSelf) {
600 throw new Error('can not be initialized twice!')
601 }
602
603 /**
604 * 1. Setup Wechaty User Classes
605 */
606 this.wechatifiedContact = wechatifyContact(this)
607 this.wechatifiedContactSelf = wechatifyContactSelf(this)
608 this.wechatifiedFriendship = wechatifyFriendship(this)
609 this.wechatifiedImage = wechatifyImage(this)
610 this.wechatifiedMessage = wechatifyMessage(this)
611 this.wechatifiedMiniProgram = wechatifyMiniProgram(this)
612 this.wechatifiedRoom = wechatifyRoom(this)
613 this.wechatifiedRoomInvitation = wechatifyRoomInvitation(this)
614 this.wechatifiedTag = wechatifyTag(this)
615 this.wechatifiedUrlLink = wechatifyUrlLink(this)
616
617 this.puppet = puppet
618 }
619
620 /**
621 * Start the bot, return Promise.
622 *
623 * @returns {Promise<void>}
624 * @description
625 * When you start the bot, bot will begin to login, need you WeChat scan qrcode to login
626 * > Tips: All the bot operation needs to be triggered after start() is done
627 * @example
628 * await bot.start()
629 * // do other stuff with bot here
630 */
631 public async start (): Promise<void> {
632 log.verbose('Wechaty', '<%s>(%s) start() v%s is starting...',
633 this.options.puppet || config.systemPuppetName(),
634 this.options.name || '',
635 this.version(),
636 )
637 log.verbose('Wechaty', 'id: %s', this.id)
638
639 if (this.state.on()) {
640 log.silly('Wechaty', 'start() on a starting/started instance')
641 await this.state.ready('on')
642 log.silly('Wechaty', 'start() state.ready() resolved')
643 return
644 }
645
646 this.readyState.off(true)
647
648 if (this.lifeTimer) {
649 throw new Error('start() lifeTimer exist')
650 }
651
652 this.state.on('pending')
653
654 try {
655 if (!this.memory) {
656 this.memory = new MemoryCard(this.options.name)
657 }
658
659 try {
660 await this.memory.load()
661 } catch (e) {
662 log.silly('Wechaty', 'start() memory.load() had already loaded')
663 }
664
665 await this.initPuppet()
666 await this.puppet.start()
667
668 if (this.options.ioToken) {
669 this.io = new Io({
670 token : this.options.ioToken,
671 wechaty : this,
672 })
673 await this.io.start()
674 }
675
676 } catch (e) {
677 console.error(e)
678 log.error('Wechaty', 'start() exception: %s', e && e.message)
679 Raven.captureException(e)
680 this.emit('error', e)
681
682 try {
683 await this.stop()
684 } catch (e) {
685 log.error('Wechaty', 'start() stop() exception: %s', e && e.message)
686 Raven.captureException(e)
687 this.emit('error', e)
688 }
689 return
690 }
691
692 this.on('heartbeat', () => this.memoryCheck())
693
694 this.lifeTimer = setInterval(() => {
695 log.silly('Wechaty', 'start() setInterval() this timer is to keep Wechaty running...')
696 }, 1000 * 60 * 60)
697
698 this.state.on(true)
699 this.emit('start')
700 }
701
702 /**
703 * Stop the bot
704 *
705 * @returns {Promise<void>}
706 * @example
707 * await bot.stop()
708 */
709 public async stop (): Promise<void> {
710 log.verbose('Wechaty', '<%s> stop() v%s is stopping ...',
711 this.options.puppet || config.systemPuppetName(),
712 this.version(),
713 )
714
715 /**
716 * Uninstall Plugins
717 * no matter the state is `ON` or `OFF`.
718 */
719 while (this.pluginUninstallerList.length > 0) {
720 const uninstaller = this.pluginUninstallerList.pop()
721 if (uninstaller) uninstaller()
722 }
723
724 if (this.state.off()) {
725 log.silly('Wechaty', 'stop() on an stopping/stopped instance')
726 await this.state.ready('off')
727 log.silly('Wechaty', 'stop() state.ready(off) resolved')
728 return
729 }
730
731 this.readyState.off(true)
732
733 this.state.off('pending')
734
735 if (this.lifeTimer) {
736 clearInterval(this.lifeTimer)
737 this.lifeTimer = undefined
738 }
739
740 try {
741 await this.puppet.stop()
742 } catch (e) {
743 log.warn('Wechaty', 'stop() puppet.stop() exception: %s', e.message)
744 }
745
746 try {
747 if (this.io) {
748 await this.io.stop()
749 this.io = undefined
750 }
751
752 } catch (e) {
753 log.error('Wechaty', 'stop() exception: %s', e.message)
754 Raven.captureException(e)
755 this.emit('error', e)
756 }
757
758 this.state.off(true)
759 this.emit('stop')
760 }
761
762 public async ready (): Promise<void> {
763 log.verbose('Wechaty', 'ready()')
764 return this.readyState.ready('on').then(() => {
765 return log.silly('Wechaty', 'ready() this.readyState.ready(on) resolved')
766 })
767 }
768
769 /**
770 * Logout the bot
771 *
772 * @returns {Promise<void>}
773 * @example
774 * await bot.logout()
775 */
776 public async logout (): Promise<void> {
777 log.verbose('Wechaty', 'logout()')
778
779 try {
780 await this.puppet.logout()
781 } catch (e) {
782 log.error('Wechaty', 'logout() exception: %s', e.message)
783 Raven.captureException(e)
784 throw e
785 }
786 }
787
788 /**
789 * Get the logon / logoff state
790 *
791 * @returns {boolean}
792 * @example
793 * if (bot.logonoff()) {
794 * console.log('Bot logged in')
795 * } else {
796 * console.log('Bot not logged in')
797 * }
798 */
799 public logonoff (): boolean {
800 try {
801 return this.puppet.logonoff()
802 } catch (e) {
803 // https://github.com/wechaty/wechaty/issues/1878
804 return false
805 }
806 }
807
808 /**
809 * @description
810 * Should use {@link Wechaty#userSelf} instead
811 * @deprecated Use `userSelf()` instead
812 * @ignore
813 */
814 public self (): Contact {
815 log.warn('Wechaty', 'self() DEPRECATED. use userSelf() instead.')
816 return this.userSelf()
817 }
818
819 /**
820 * Get current user
821 *
822 * @returns {ContactSelf}
823 * @example
824 * const contact = bot.userSelf()
825 * console.log(`Bot is ${contact.name()}`)
826 */
827 public userSelf (): ContactSelf {
828 const userId = this.puppet.selfId()
829 const user = this.ContactSelf.load(userId)
830 return user
831 }
832
833 public async say (text: string) : Promise<void>
834 public async say (contact: Contact) : Promise<void>
835 public async say (file: FileBox) : Promise<void>
836 public async say (mini: MiniProgram) : Promise<void>
837 public async say (url: UrlLink) : Promise<void>
838
839 public async say (...args: never[]): Promise<never>
840
841 /**
842 * Send message to userSelf, in other words, bot send message to itself.
843 * > Tips:
844 * This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table)
845 *
846 * @param {(string | Contact | FileBox | UrlLink | MiniProgram)} something
847 * send text, Contact, or file to bot. </br>
848 * You can use {@link https://www.npmjs.com/package/file-box|FileBox} to send file
849 *
850 * @returns {Promise<void>}
851 *
852 * @example
853 * const bot = new Wechaty()
854 * await bot.start()
855 * // after logged in
856 *
857 * // 1. send text to bot itself
858 * await bot.say('hello!')
859 *
860 * // 2. send Contact to bot itself
861 * const contact = await bot.Contact.find()
862 * await bot.say(contact)
863 *
864 * // 3. send Image to bot itself from remote url
865 * import { FileBox } from 'wechaty'
866 * const fileBox = FileBox.fromUrl('https://wechaty.github.io/wechaty/images/bot-qr-code.png')
867 * await bot.say(fileBox)
868 *
869 * // 4. send Image to bot itself from local file
870 * import { FileBox } from 'wechaty'
871 * const fileBox = FileBox.fromFile('/tmp/text.jpg')
872 * await bot.say(fileBox)
873 *
874 * // 5. send Link to bot itself
875 * const linkPayload = new UrlLink ({
876 * description : 'WeChat Bot SDK for Individual Account, Powered by TypeScript, Docker, and Love',
877 * thumbnailUrl: 'https://avatars0.githubusercontent.com/u/25162437?s=200&v=4',
878 * title : 'Welcome to Wechaty',
879 * url : 'https://github.com/wechaty/wechaty',
880 * })
881 * await bot.say(linkPayload)
882 *
883 * // 6. send MiniProgram to bot itself
884 * const miniPayload = new MiniProgram ({
885 * username : 'gh_xxxxxxx', //get from mp.weixin.qq.com
886 * appid : '', //optional, get from mp.weixin.qq.com
887 * title : '', //optional
888 * pagepath : '', //optional
889 * description : '', //optional
890 * thumbnailurl : '', //optional
891 * })
892 * await bot.say(miniPayload)
893 */
894
895 public async say (
896 something: string
897 | Contact
898 | FileBox
899 | MiniProgram
900 | UrlLink
901 ): Promise<void> {
902 log.verbose('Wechaty', 'say(%s)', something)
903 // huan: to make TypeScript happy
904 await this.userSelf().say(something as any)
905 }
906
907 /**
908 * @ignore
909 */
910 public static version (gitHash = false): string {
911 if (gitHash && GIT_COMMIT_HASH) {
912 return `#git[${GIT_COMMIT_HASH}]`
913 }
914 return VERSION
915 }
916
917 /**
918 * @ignore
919 * Return version of Wechaty
920 *
921 * @param {boolean} [forceNpm=false] - If set to true, will only return the version in package.json. </br>
922 * Otherwise will return git commit hash if .git exists.
923 * @returns {string} - the version number
924 * @example
925 * console.log(Wechaty.instance().version()) // return '#git[af39df]'
926 * console.log(Wechaty.instance().version(true)) // return '0.7.9'
927 */
928 public version (forceNpm = false): string {
929 return Wechaty.version(forceNpm)
930 }
931
932 /**
933 * @ignore
934 */
935 public static async sleep (millisecond: number): Promise<void> {
936 await new Promise(resolve => {
937 setTimeout(resolve, millisecond)
938 })
939 }
940
941 /**
942 * @ignore
943 */
944 public async sleep (millisecond: number): Promise<void> {
945 return Wechaty.sleep(millisecond)
946 }
947
948 /**
949 * @private
950 */
951 public ding (data?: string): void {
952 log.silly('Wechaty', 'ding(%s)', data || '')
953
954 try {
955 this.puppet.ding(data)
956 } catch (e) {
957 log.error('Wechaty', 'ding() exception: %s', e.message)
958 Raven.captureException(e)
959 throw e
960 }
961 }
962
963 /**
964 * @ignore
965 */
966 private memoryCheck (minMegabyte = 4): void {
967 const freeMegabyte = Math.floor(os.freemem() / 1024 / 1024)
968 log.silly('Wechaty', 'memoryCheck() free: %d MB, require: %d MB',
969 freeMegabyte, minMegabyte,
970 )
971
972 if (freeMegabyte < minMegabyte) {
973 const e = new Error(`memory not enough: free ${freeMegabyte} < require ${minMegabyte} MB`)
974 log.warn('Wechaty', 'memoryCheck() %s', e.message)
975 this.emit('error', e)
976 }
977 }
978
979 /**
980 * @ignore
981 */
982 public async reset (reason?: string): Promise<void> {
983 log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
984 await this.puppet.stop()
985 await this.puppet.start()
986 }
987
988 public unref (): void {
989 log.verbose('Wechaty', 'unref()')
990
991 if (this.lifeTimer) {
992 this.lifeTimer.unref()
993 }
994
995 this.puppet.unref()
996 }
997
998}