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