oicq-icalingua-plus-plus 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +73 -0
  3. package/bin/oicq +2 -0
  4. package/http-api/README.md +187 -0
  5. package/http-api/api.js +217 -0
  6. package/http-api/config.sample.js +44 -0
  7. package/http-api/core.js +480 -0
  8. package/http-api/cq-notice.js +39 -0
  9. package/http-api/filter.js +128 -0
  10. package/http-api/index.js +39 -0
  11. package/index.d.ts +1214 -0
  12. package/index.js +34 -0
  13. package/lib/algo/jce/index.js +63 -0
  14. package/lib/algo/jce/jce.js +341 -0
  15. package/lib/algo/pb.js +159 -0
  16. package/lib/algo/protobuf.min.js +8 -0
  17. package/lib/algo/tea.js +121 -0
  18. package/lib/client-ext.js +312 -0
  19. package/lib/client-net.js +147 -0
  20. package/lib/client.js +594 -0
  21. package/lib/common.js +252 -0
  22. package/lib/core/friendlist.js +415 -0
  23. package/lib/core/nessy.js +148 -0
  24. package/lib/core/ref.d.ts +1 -0
  25. package/lib/core/sysmsg.js +388 -0
  26. package/lib/core/troop.js +586 -0
  27. package/lib/device.js +181 -0
  28. package/lib/exception.js +57 -0
  29. package/lib/message/builder.js +1093 -0
  30. package/lib/message/chat.js +276 -0
  31. package/lib/message/face.js +293 -0
  32. package/lib/message/file.js +425 -0
  33. package/lib/message/history.js +73 -0
  34. package/lib/message/image.js +533 -0
  35. package/lib/message/multi.js +242 -0
  36. package/lib/message/music.js +172 -0
  37. package/lib/message/parser.js +774 -0
  38. package/lib/message/ptt.js +389 -0
  39. package/lib/oicq.js +427 -0
  40. package/lib/onlinepush.js +616 -0
  41. package/lib/ref.d.ts +177 -0
  42. package/lib/service.js +204 -0
  43. package/lib/wtlogin/ecdh.js +15 -0
  44. package/lib/wtlogin/login-password.js +215 -0
  45. package/lib/wtlogin/login-qrcode.js +249 -0
  46. package/lib/wtlogin/tlv.js +331 -0
  47. package/lib/wtlogin/writer.js +88 -0
  48. package/lib/wtlogin/wt.js +422 -0
  49. package/package.json +50 -0
  50. package/util.js +115 -0
package/lib/client.js ADDED
@@ -0,0 +1,594 @@
1
+ /**
2
+ * api
3
+ */
4
+ "use strict";
5
+ const version = require("../package.json");
6
+ const { EventEmitter } = require("events");
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const os = require("os");
10
+ const { exec } = require("child_process");
11
+ const { randomBytes } = require("crypto");
12
+ const log4js = require("log4js");
13
+ const Network = require("./client-net");
14
+ const { getApkInfo, getDeviceInfo } = require("./device");
15
+ const { timestamp, md5, BUF0 } = require("./common");
16
+ const { onlineListener, offlineListener, packetListener, networkErrorListener } = require("./oicq");
17
+ const frdlst = require("./core/friendlist");
18
+ const sysmsg = require("./core/sysmsg");
19
+ const troop = require("./core/troop");
20
+ const nessy = require("./core/nessy");
21
+ const { WtLogin } = require("./wtlogin/wt");
22
+ const chat = require("./message/chat");
23
+ const multi = require("./message/multi");
24
+ const { Gfs } = require("./message/file");
25
+ const { getErrorMessage, TimeoutError } = require("./exception");
26
+
27
+ function buildApiRet(retcode, data = null, error = null) {
28
+ data = data ? data : null;
29
+ error = error ? error : null;
30
+ const status = retcode ? (retcode === 1 ? "async" : "failed") : "ok";
31
+ return {
32
+ retcode, data, status, error
33
+ };
34
+ }
35
+
36
+ const platforms = {
37
+ 1: "Android",
38
+ 2: "aPad",
39
+ 3: "Watch",
40
+ 4: "MacOS",
41
+ 5: "iPad",
42
+ };
43
+
44
+ /** 客户端已上线状态 */
45
+ const STATUS_ONLINE = Symbol("ONLINE");
46
+ /** socket未连接状态 */
47
+ const STATUS_OFFLINE = Symbol("OFFLINE");
48
+ /** socket已连接,但客户端未上线状态 */
49
+ const STATUS_PENDING = Symbol("PENDING");
50
+
51
+ class Client extends EventEmitter {
52
+
53
+ status = STATUS_OFFLINE;
54
+ online_status = 0;
55
+ nickname = "";
56
+ age = 0;
57
+ sex = "unknown";
58
+
59
+ fl = new Map; //friendList
60
+ sl = new Map; //strangerList
61
+ gl = new Map; //groupList
62
+ gml = new Map; //groupMemberList
63
+
64
+ seq_id = 0;
65
+ handlers = new Map; //存放响应包的回调
66
+ seq_cache = new Map; //一分钟内的缓存
67
+
68
+ sig = {
69
+ srm_token: BUF0,
70
+ tgt: BUF0,
71
+ tgt_key: BUF0,
72
+ st_key: BUF0,
73
+ st_web_sig: BUF0,
74
+ skey: BUF0,
75
+ d2: BUF0,
76
+ d2key: BUF0,
77
+ sig_key: BUF0,
78
+ ticket_key: BUF0,
79
+ device_token: BUF0,
80
+ emp_time: timestamp(),
81
+ };
82
+ _cookies = { };
83
+
84
+ sync_finished = false;
85
+ sync_cookie;
86
+ const1 = randomBytes(4).readUInt32BE();
87
+ const2 = randomBytes(4).readUInt32BE();
88
+ const3 = randomBytes(1)[0];
89
+
90
+ _stat = {
91
+ start_time: timestamp(),
92
+ lost_times: 0,
93
+ recv_pkt_cnt: 0,
94
+ sent_pkt_cnt: 0,
95
+ lost_pkt_cnt: 0,
96
+ recv_msg_cnt: 0,
97
+ sent_msg_cnt: 0,
98
+ msg_cnt_per_min: 0,
99
+ remote_ip: "",
100
+ remote_port: 0,
101
+ };
102
+
103
+ blacklist = new Set;
104
+
105
+ storage = {};
106
+
107
+ _socket = new Network(this);
108
+ _wt = new WtLogin(this);
109
+
110
+ get [Symbol.toStringTag]() {
111
+ return "OicqClient";
112
+ }
113
+ get uin() {
114
+ return this._uin;
115
+ }
116
+ get stat() {
117
+ this._stat.msg_cnt_per_min = this._calcMsgCnt();
118
+ this._stat.remote_ip = this._socket.remoteAddress;
119
+ this._stat.remote_port = this._socket.remotePort;
120
+ return this._stat;
121
+ }
122
+ get bkn() {
123
+ let bkn = 5381;
124
+ for (let v of this.sig.skey)
125
+ bkn = bkn + (bkn << 5) + v;
126
+ bkn &= 2147483647;
127
+ return bkn;
128
+ }
129
+ cookies = new Proxy(this._cookies, {
130
+ get: (obj, domain) => {
131
+ const cookie = `uin=o${this.uin}; skey=${this.sig.skey};`;
132
+ if (!obj[domain])
133
+ return cookie;
134
+ return `${cookie} p_uin=o${this.uin}; p_skey=${obj[domain]};`;
135
+ }
136
+ });
137
+
138
+ /**
139
+ * @param {number} uin
140
+ * @param {import("./ref").ConfBot} config
141
+ */
142
+ constructor(uin, config) {
143
+ super();
144
+ this._uin = uin;
145
+
146
+ config = {
147
+ platform: 1,
148
+ log_level: "info",
149
+ kickoff: false,
150
+ brief: false,
151
+ ignore_self: true,
152
+ resend: true,
153
+ reconn_interval: 5,
154
+ internal_cache_life: 3600,
155
+ auto_server: true,
156
+ data_dir: path.join(require.main ? require.main.path : process.cwd(), "data"),
157
+ ffmpeg_path: "ffmpeg",
158
+ ffprobe_path: "ffprobe",
159
+ ...config
160
+ };
161
+ this.config = config;
162
+ this.dir = createDataDir(config.data_dir, uin);
163
+ this.logger = log4js.getLogger(`[${platforms[config.platform]||"Android"}:${uin}]`);
164
+ this.logger.level = config.log_level;
165
+
166
+ this.logger.mark("----------");
167
+ this.logger.mark(`Package Version: oicq@${version.version} (Released on ${version.upday})`);
168
+ this.logger.mark("View Changelogs:https://github.com/takayama-lily/oicq/releases");
169
+ this.logger.mark("----------");
170
+
171
+ const filepath = path.join(this.dir, `device-${uin}.json`);
172
+ if (!fs.existsSync(filepath))
173
+ this.logger.mark("创建了新的设备文件:" + filepath);
174
+ this.device = getDeviceInfo(filepath, this.uin);
175
+ this.apk = getApkInfo(config.platform);
176
+
177
+ this.on("internal.offline", offlineListener);
178
+ this.on("internal.login", onlineListener);
179
+ this.on("internal.packet", packetListener);
180
+ this.on("internal.network", networkErrorListener);
181
+ }
182
+
183
+ /**
184
+ * 连接服务器并执行回调
185
+ * 如果已经连接则立刻执行回调
186
+ * @private
187
+ * @param {Function} cb
188
+ */
189
+ _connect(cb) {
190
+ if (this.status !== STATUS_OFFLINE)
191
+ return cb();
192
+ this._socket.join(() => {
193
+ this.status = STATUS_PENDING;
194
+ cb();
195
+ });
196
+ }
197
+
198
+ /**
199
+ * 调用api前的一层封装
200
+ * @private
201
+ * @param {Function} fn
202
+ * @param {Array} params
203
+ */
204
+ async _useProtocol(fn, params) {
205
+ if (!this.isOnline() || !this.sync_finished) {
206
+ this.logger.error("Invoke failed, because -> client is not online")
207
+ return buildApiRet(104, null, { code: -1, message: "client not online" });
208
+ }
209
+ try {
210
+ const rsp = await fn.apply(this, params);
211
+ if (!rsp)
212
+ return buildApiRet(1);
213
+ if (rsp.result !== 0)
214
+ return buildApiRet(102, null,
215
+ {
216
+ code: rsp.result,
217
+ message: rsp.emsg ? rsp.emsg : getErrorMessage(fn, rsp.result)
218
+ }
219
+ );
220
+ else
221
+ return buildApiRet(0, rsp.data);
222
+ } catch (e) {
223
+ this.logger.error("Invoke failed, because -> " + e.message);
224
+ if (e instanceof TimeoutError)
225
+ return buildApiRet(103, null, { code: -1, message: "packet timeout" });
226
+ this.logger.debug(e);
227
+ return buildApiRet(100, null, { code: -1, message: e.message });
228
+ }
229
+ }
230
+
231
+ /**
232
+ * 计算每分钟消息数
233
+ * @private
234
+ */
235
+ _calcMsgCnt() {
236
+ let cnt = 0;
237
+ for (let [time, set] of this.seq_cache) {
238
+ if (timestamp() - time >= 60)
239
+ this.seq_cache.delete(time);
240
+ else
241
+ cnt += set.size;
242
+ }
243
+ return cnt;
244
+ }
245
+
246
+ ///////////////////////////////////////////////////
247
+
248
+ login(password) {
249
+ if (this.isOnline())
250
+ return;
251
+ if (password) {
252
+ let password_md5;
253
+ if (typeof password === "string")
254
+ password_md5 = Buffer.from(password, "hex");
255
+ else if (password instanceof Uint8Array)
256
+ password_md5 = Buffer.from(password);
257
+ if (password_md5 && password_md5.length === 16)
258
+ this.password_md5 = password_md5;
259
+ else
260
+ this.password_md5 = md5(String(password));
261
+ }
262
+ this._connect(() => {
263
+ this._wt.passwordLogin();
264
+ });
265
+ }
266
+
267
+ captchaLogin() { }
268
+
269
+ sliderLogin(ticket) {
270
+ this._connect(() => {
271
+ this._wt.sliderLogin(ticket);
272
+ });
273
+ }
274
+
275
+ sendSMSCode() {
276
+ this._connect(() => {
277
+ this._wt.sendSMSCode();
278
+ });
279
+ }
280
+
281
+ submitSMSCode(code) {
282
+ this._connect(() => {
283
+ this._wt.submitSMSCode(code);
284
+ });
285
+ }
286
+
287
+ terminate() {
288
+ if (this.status === STATUS_ONLINE)
289
+ this.status = STATUS_PENDING;
290
+ this._socket.destroy();
291
+ }
292
+
293
+ async logout() {
294
+ if (this.isOnline()) {
295
+ try {
296
+ await this._wt.register(true);
297
+ } catch { }
298
+ }
299
+ this.terminate();
300
+ await new Promise(resolve => this.once("internal.logout", resolve));
301
+ }
302
+
303
+ isOnline() {
304
+ return this.status === STATUS_ONLINE;
305
+ }
306
+
307
+ ///////////////////////////////////////////////////
308
+
309
+ setOnlineStatus(status) {
310
+ return this._useProtocol(nessy.setStatus, arguments);
311
+ }
312
+
313
+ getFriendList() {
314
+ return buildApiRet(0, this.fl);
315
+ }
316
+ getStrangerList() {
317
+ return buildApiRet(0, this.sl);
318
+ }
319
+ getGroupList() {
320
+ return buildApiRet(0, this.gl);
321
+ }
322
+
323
+ async reloadFriendList() {
324
+ const ret = await this._useProtocol(frdlst.initFL, arguments);
325
+ this.sync_finished = true;
326
+ this.pbGetMsg();
327
+ return ret;
328
+ }
329
+ async reloadGroupList() {
330
+ const ret = await this._useProtocol(frdlst.initGL, arguments);
331
+ this.sync_finished = true;
332
+ this.pbGetMsg();
333
+ return ret;
334
+ }
335
+
336
+ getGroupMemberList(group_id, no_cache = false) {
337
+ return this._useProtocol(frdlst.getGML, arguments);
338
+ }
339
+ getStrangerInfo(user_id, no_cache = false) {
340
+ return this._useProtocol(frdlst.getSI, arguments);
341
+ }
342
+ getGroupInfo(group_id, no_cache = false) {
343
+ return this._useProtocol(frdlst.getGI, arguments);
344
+ }
345
+ getGroupMemberInfo(group_id, user_id, no_cache = false) {
346
+ return this._useProtocol(frdlst.getGMI, arguments);
347
+ }
348
+
349
+ ///////////////////////////////////////////////////
350
+
351
+ sendPrivateMsg(user_id, message = "", auto_escape = false) {
352
+ return this._useProtocol(chat.sendMsg, [user_id, message, auto_escape, 0]);
353
+ }
354
+ sendGroupMsg(group_id, message = "", auto_escape = false) {
355
+ return this._useProtocol(chat.sendMsg, [group_id, message, auto_escape, 1]);
356
+ }
357
+ sendDiscussMsg(discuss_id, message = "", auto_escape = false) {
358
+ return this._useProtocol(chat.sendMsg, [discuss_id, message, auto_escape, 2]);
359
+ }
360
+ sendTempMsg(group_id, user_id, message = "", auto_escape = false) {
361
+ return this._useProtocol(chat.sendTempMsg, arguments);
362
+ }
363
+ deleteMsg(message_id) {
364
+ return this._useProtocol(chat.recallMsg, arguments);
365
+ }
366
+ reportReaded(message_id) {
367
+ return this._useProtocol(chat.reportReaded, arguments);
368
+ }
369
+ getMsg(message_id) {
370
+ return this._useProtocol(chat.getOneMsg, arguments);
371
+ }
372
+ getChatHistory(message_id, count = 10) {
373
+ return this._useProtocol(chat.getMsgs, arguments);
374
+ }
375
+ getForwardMsg(id) {
376
+ return this._useProtocol(chat.getForwardMsg, arguments);
377
+ }
378
+ makeForwardMsg(fakes, dm = false) {
379
+ return this._useProtocol(multi.makeForwardMsg, arguments);
380
+ }
381
+
382
+ ///////////////////////////////////////////////////
383
+
384
+ setGroupAnonymousBan(group_id, flag, duration = 1800) {
385
+ return this._useProtocol(troop.muteAnonymous, arguments);
386
+ }
387
+ setGroupAnonymous(group_id, enable = true) {
388
+ return this._useProtocol(troop.setAnonymous, arguments);
389
+ }
390
+ setGroupWholeBan(group_id, enable = true) {
391
+ return this.setGroupSetting(group_id, "shutupTime", enable ? 0xffffffff : 0);
392
+ }
393
+ setGroupName(group_id, group_name) {
394
+ return this.setGroupSetting(group_id, "ingGroupName", String(group_name));
395
+ }
396
+ sendGroupNotice(group_id, content) {
397
+ return this.setGroupSetting(group_id, "ingGroupMemo", String(content));
398
+ }
399
+ setGroupSetting(group_id, k, v) {
400
+ return this._useProtocol(troop.setting, arguments);
401
+ }
402
+ setGroupAdmin(group_id, user_id, enable = true) {
403
+ return this._useProtocol(troop.setAdmin, arguments);
404
+ }
405
+ setGroupSpecialTitle(group_id, user_id, special_title = "", duration = -1) {
406
+ return this._useProtocol(troop.setTitle, arguments);
407
+ }
408
+ setGroupCard(group_id, user_id, card = "") {
409
+ return this._useProtocol(troop.setCard, arguments);
410
+ }
411
+ setGroupKick(group_id, user_id, reject_add_request = false) {
412
+ return this._useProtocol(troop.kickMember, arguments);
413
+ }
414
+ setGroupBan(group_id, user_id, duration = 1800) {
415
+ return this._useProtocol(troop.muteMember, arguments);
416
+ }
417
+ setGroupLeave(group_id, is_dismiss = false) {
418
+ return this._useProtocol(troop.quitGroup, arguments);
419
+ }
420
+ sendGroupPoke(group_id, user_id) {
421
+ return this._useProtocol(troop.pokeMember, arguments);
422
+ }
423
+
424
+ ///////////////////////////////////////////////////
425
+
426
+ setFriendAddRequest(flag, approve = true, remark = "", block = false) {
427
+ return this._useProtocol(sysmsg.friendAction, arguments);
428
+ }
429
+ setGroupAddRequest(flag, approve = true, reason = "", block = false) {
430
+ return this._useProtocol(sysmsg.groupAction, arguments);
431
+ }
432
+ getSystemMsg() {
433
+ return this._useProtocol(sysmsg.getSysMsg, arguments);
434
+ }
435
+
436
+ addGroup(group_id, comment = "") {
437
+ return this._useProtocol(troop.addGroup, arguments);
438
+ }
439
+ addFriend(group_id, user_id, comment = "") {
440
+ return this._useProtocol(troop.addFriend, arguments);
441
+ }
442
+ deleteFriend(user_id, block = true) {
443
+ return this._useProtocol(troop.delFriend, arguments);
444
+ }
445
+ inviteFriend(group_id, user_id) {
446
+ return this._useProtocol(troop.inviteFriend, arguments);
447
+ }
448
+
449
+ sendLike(user_id, times = 1) {
450
+ return this._useProtocol(nessy.sendLike, arguments);
451
+ }
452
+ setNickname(nickname) {
453
+ return this._useProtocol(troop.setProfile, [0x14E22, String(nickname)]);
454
+ }
455
+ setDescription(description = "") {
456
+ return this._useProtocol(troop.setProfile, [0x14E33, String(description)]);
457
+ }
458
+ setGender(gender) {
459
+ gender = parseInt(gender);
460
+ if (![0, 1, 2].includes(gender))
461
+ return buildApiRet(100);
462
+ return this._useProtocol(troop.setProfile, [0x14E29, Buffer.from([gender])]);
463
+ }
464
+ async setBirthday(birthday) {
465
+ try {
466
+ birthday = String(birthday).replace(/[^\d]/g, "");
467
+ const buf = Buffer.alloc(4);
468
+ buf.writeUInt16BE(parseInt(birthday.substr(0, 4)));
469
+ buf.writeUInt8(parseInt(birthday.substr(4, 2)), 2);
470
+ buf.writeUInt8(parseInt(birthday.substr(6, 2)), 3);
471
+ return this._useProtocol(troop.setProfile, [0x16593, buf]);
472
+ } catch (e) {
473
+ return buildApiRet(100);
474
+ }
475
+ }
476
+ setSignature(signature = "") {
477
+ return this._useProtocol(troop.setSign, arguments);
478
+ }
479
+ setPortrait(file) {
480
+ return this._useProtocol(troop.setPortrait, arguments);
481
+ }
482
+ setGroupPortrait(group_id, file) {
483
+ return this._useProtocol(troop.setGroupPortrait, arguments);
484
+ }
485
+ getLevelInfo(user_id) {
486
+ return this._useProtocol(nessy.getLevelInfo, arguments);
487
+ }
488
+
489
+ getRoamingStamp(no_cache = false) {
490
+ return this._useProtocol(nessy.getRoamingStamp, arguments);
491
+ }
492
+
493
+ getGroupNotice(group_id) {
494
+ return this._useProtocol(troop.getGroupNotice, arguments);
495
+ }
496
+
497
+ preloadImages(files) {
498
+ return this._useProtocol(chat.preloadImages, arguments);
499
+ }
500
+
501
+ ///////////////////////////////////////////////////
502
+
503
+ async getCookies(domain) {
504
+ const cookies = this.cookies[domain];
505
+ if (!cookies)
506
+ return buildApiRet(100, null, { code: -1, message: "unknown domain" });
507
+ return buildApiRet(0, { cookies });
508
+ }
509
+
510
+ async getCsrfToken() {
511
+ return buildApiRet(0, { token: this.bkn });
512
+ }
513
+
514
+ /**
515
+ * @param {String} type "image" or "record" or undefined
516
+ */
517
+ async cleanCache(type = "") {
518
+ let file, cmd;
519
+ switch (type) {
520
+ case "image":
521
+ case "record":
522
+ file = path.join(this.dir, "..", type, "*");
523
+ cmd = os.platform().includes("win") ? "del /q " : "rm -f ";
524
+ exec(cmd + '"' + file + '"', (err, stdout, stderr) => {
525
+ if (err)
526
+ return this.logger.error(err);
527
+ if (stderr)
528
+ return this.logger.error(stderr);
529
+ this.logger.info(type + " cache clear");
530
+ });
531
+ break;
532
+ case "":
533
+ this.cleanCache("image");
534
+ this.cleanCache("record");
535
+ break;
536
+ default:
537
+ return buildApiRet(100, null, { code: -1, message: "unknown type (image, record, or undefined)" });
538
+ }
539
+ return buildApiRet(1);
540
+ }
541
+
542
+ canSendImage() {
543
+ return buildApiRet(0, { yes: true });
544
+ }
545
+ canSendRecord() {
546
+ return buildApiRet(0, { yes: true });
547
+ }
548
+ getVersionInfo() {
549
+ return buildApiRet(0, version);
550
+ }
551
+ getStatus() {
552
+ return buildApiRet(0, {
553
+ online: this.isOnline(),
554
+ status: this.online_status,
555
+ remote_ip: this._socket.remoteAddress,
556
+ remote_port: this._socket.remotePort,
557
+ msg_cnt_per_min: this._calcMsgCnt(),
558
+ statistics: this.stat,
559
+ config: this.config
560
+ });
561
+ }
562
+ getLoginInfo() {
563
+ return buildApiRet(0, {
564
+ user_id: this.uin,
565
+ nickname: this.nickname,
566
+ age: this.age, sex: this.sex
567
+ });
568
+ }
569
+ acquireGfs(group_id) {
570
+ return new Gfs(this, group_id);
571
+ }
572
+ }
573
+
574
+ function createDataDir(dir, uin) {
575
+ if (!fs.existsSync(dir))
576
+ fs.mkdirSync(dir, { mode: 0o755, recursive: true });
577
+ const img_path = path.join(dir, "image");
578
+ const ptt_path = path.join(dir, "record");
579
+ const uin_path = path.join(dir, String(uin));
580
+ if (!fs.existsSync(img_path))
581
+ fs.mkdirSync(img_path);
582
+ if (!fs.existsSync(ptt_path))
583
+ fs.mkdirSync(ptt_path);
584
+ if (!fs.existsSync(uin_path))
585
+ fs.mkdirSync(uin_path, { mode: 0o755 });
586
+ return uin_path;
587
+ }
588
+
589
+ module.exports = {
590
+ Client,
591
+ STATUS_ONLINE, STATUS_OFFLINE, STATUS_PENDING
592
+ };
593
+
594
+ require("./client-ext");