iobroker.imap 0.0.4

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.
package/main.js ADDED
@@ -0,0 +1,1148 @@
1
+ "use strict";
2
+
3
+ /*
4
+ * Created with @iobroker/create-adapter v2.3.0
5
+ */
6
+
7
+ // The adapter-core module gives you access to the core ioBroker functions
8
+ // you need to create an adapter
9
+ const utils = require("@iobroker/adapter-core");
10
+
11
+ // Load your modules here, e.g.:
12
+ const { MailListener } = require("./lib/listener");
13
+ const helper = require("./lib/helper");
14
+ const tl = require("./lib/translator.js");
15
+ const format = require("util").format;
16
+ const { convert } = require("html-to-text");
17
+ const FORBIDDEN_CHARS = /[\][züäöÜÄÖ$@߀*:.,;'"`<>\\\s?]/g;
18
+
19
+ class Imap extends utils.Adapter {
20
+ /**
21
+ * @param {Partial<utils.AdapterOptions>} [options={}]
22
+ */
23
+ constructor(options) {
24
+ super({
25
+ ...options,
26
+ name: "imap",
27
+ });
28
+ this.on("ready", this.onReady.bind(this));
29
+ this.on("stateChange", this.onStateChange.bind(this));
30
+ this.on("unload", this.onUnload.bind(this));
31
+ this.on("message", this.onMessage.bind(this));
32
+ this.createDataPoint = helper.createDataPoint;
33
+ this.createHost = helper.createHost;
34
+ this.createMails = helper.createMails;
35
+ this.createRemote = helper.createRemote;
36
+ this.createinbox = helper.createinbox;
37
+ this.qualityInterval = null;
38
+ this.statusInterval = null;
39
+ this.sleepTimer = null;
40
+ this.double_call = {};
41
+ this.save_json = {};
42
+ this.save_seqno = {};
43
+ this.clients = {};
44
+ this.clientsRaw = {};
45
+ this.clientsHTML = {};
46
+ this.clientsRows = {};
47
+ this.restartIMAPConnection = {};
48
+ this.clientsID = [];
49
+ this.countOnline = 0;
50
+ this.lang = "de";
51
+ }
52
+
53
+ /**
54
+ * Is called when databases are connected and adapter received configuration.
55
+ */
56
+ async onReady() {
57
+ // Initialize your adapter here
58
+ const devices = {};
59
+ if (Object.keys(this.config.hosts).length === 0) {
60
+ this.log_translator("info", "No imap");
61
+ return;
62
+ }
63
+ const isChange = await this.configcheck();
64
+ if (isChange) {
65
+ this.log_translator("info", "Adapter restart");
66
+ return;
67
+ }
68
+ const obj = await this.getForeignObjectAsync("system.config");
69
+ if (obj && obj.common && obj.common.language) {
70
+ try {
71
+ this.lang = obj.common.language === this.lang ? this.lang : obj.common.language;
72
+ } catch (e) {
73
+ this.log_translator("info", "try", `getForeignObjectAsync: ${e}`);
74
+ }
75
+ }
76
+ devices["data"] = this.config.hosts;
77
+ for (const dev of devices.data) {
78
+ if (!dev.activ) {
79
+ continue;
80
+ }
81
+ if (dev.inbox == "") {
82
+ this.log_translator("info", "no inbox");
83
+ continue;
84
+ }
85
+ if (dev.host == "") {
86
+ this.log_translator("info", "no host");
87
+ continue;
88
+ }
89
+ if (dev.port == "") {
90
+ dev.port = 993;
91
+ this.log_translator("info", "missing port");
92
+ }
93
+ if (dev.user == "") {
94
+ this.log_translator("info", "missing username");
95
+ continue;
96
+ } else {
97
+ dev["username"] = dev.user;
98
+ dev.user = dev.user.replace(FORBIDDEN_CHARS, "_");
99
+ }
100
+ if (dev.password != "" && dev.password.match(/<LUCKY-ESA>/gi) != null) {
101
+ dev.password = this.decrypt(dev.password.split("<LUCKY-ESA>")[1]);
102
+ } else {
103
+ this.log_translator("info", "missing password");
104
+ continue;
105
+ }
106
+ this.clientsHTML[dev.user] = {};
107
+ this.restartIMAPConnection[dev.user] = null;
108
+ this.clientsRows[dev.user] = "";
109
+ this.clients[dev.user] = null;
110
+ this.clientsRaw[dev.user] = dev;
111
+ this.save_json[dev.user] = [];
112
+ this.save_seqno[dev.user] = [];
113
+ this.clientsID.push(dev.user);
114
+ this.log_translator("info", "create device", dev.user);
115
+ await this.createHost(dev);
116
+ this.log_translator("info", "check mailfolder", dev.user);
117
+ await this.checkMailFolder(dev);
118
+ this.log_translator("info", "create mails", dev.user);
119
+ await this.createMails(dev);
120
+ this.log_translator("info", "create remote", dev.user);
121
+ await this.createRemote(dev);
122
+ this.log_translator("info", "read html", dev.user);
123
+ await this.readHTML(dev);
124
+ await this.imap_connection(dev);
125
+ this.subscribeStates(`${dev.user}.remote.*`);
126
+ this.cleanupQuality();
127
+ }
128
+ this.log_translator("info", "IMAP check start");
129
+ await this.checkDeviceFolder();
130
+ this.qualityInterval = this.setInterval(() => {
131
+ this.cleanupQuality();
132
+ }, 60 * 60 * 24 * 1000);
133
+ this.statusInterval = this.setInterval(() => {
134
+ this.connectionCheck();
135
+ }, 60 * 60 * 1000);
136
+ }
137
+
138
+ async connectionCheck() {
139
+ for (const dev of this.clientsID) {
140
+ if (this.clients[dev] != null) {
141
+ const check = await this.clients[dev].imap_namespaces();
142
+ this.log_translator("debug", "IMAP namespaces", dev, JSON.stringify(check));
143
+ const deli = await this.clients[dev].imap_delimiter();
144
+ this.log_translator("debug", "IMAP delimiter", dev, JSON.stringify(deli));
145
+ const state = await this.clients[dev].imap_state();
146
+ this.log_translator("debug", "IMAP connection", dev, state);
147
+ } else {
148
+ this.log_translator("info", "No connection", dev);
149
+ }
150
+ }
151
+ }
152
+
153
+ async readHTML(dev) {
154
+ try {
155
+ const all_dp = await this.getObjectListAsync({
156
+ startkey: `${this.namespace}.${dev.user}.remote.html`,
157
+ endkey: `${this.namespace}.${dev.user}.remote.html\u9999`,
158
+ });
159
+ for (const element of all_dp.rows) {
160
+ if (element && element.value && element.value.type && element.value.type === "state") {
161
+ const last = element.id.split(".").pop();
162
+ const html = await this.getStateAsync(`${dev.user}.remote.html.${last}`);
163
+ if (last != undefined) {
164
+ this.clientsHTML[dev.user][last] = html?.val;
165
+ }
166
+ }
167
+ }
168
+ } catch (e) {
169
+ this.log_translator("error", "try", `readHTML: ${e}`);
170
+ }
171
+ }
172
+
173
+ async checkMailFolder(dev) {
174
+ try {
175
+ const all_dp = await this.getObjectListAsync({
176
+ startkey: `${this.namespace}.${dev.user}.email`,
177
+ endkey: `${this.namespace}.${dev.user}.email\u9999`,
178
+ });
179
+ let change = false;
180
+ for (const element of all_dp.rows) {
181
+ if (element && element.value && element.value.type && element.value.type === "folder") {
182
+ const last = element.id.split(".").pop();
183
+ if (last === "email") {
184
+ if (
185
+ element.value &&
186
+ element.value.native &&
187
+ element.value.native.id &&
188
+ dev.maxi < element.value.native.id
189
+ ) {
190
+ this.log_translator("debug", "Start Delete folder", element.id);
191
+ change = true;
192
+ } else {
193
+ this.log_translator("debug", "Stop Delete folder", element.id);
194
+ break;
195
+ }
196
+ } else if (change) {
197
+ if (
198
+ element.value &&
199
+ element.value.native &&
200
+ element.value.native.id &&
201
+ element.value.native.id > dev.maxi
202
+ ) {
203
+ this.log_translator("debug", "Delete folder", element.id);
204
+ await this.delObjectAsync(`${element.id}`, { recursive: true });
205
+ }
206
+ }
207
+ }
208
+ }
209
+ } catch (e) {
210
+ this.log_translator("error", "try", `checkMailFolder: ${e}`);
211
+ }
212
+ }
213
+
214
+ async checkDeviceFolder() {
215
+ try {
216
+ const devices = await this.getDevicesAsync();
217
+ for (const element of devices) {
218
+ if (this.clientsID.includes(element.common["desc"])) {
219
+ this.log_translator("debug", "Found data point", element["id"]);
220
+ } else {
221
+ this.log_translator("debug", "Deleted data point", element["id"]);
222
+ await this.delObjectAsync(`${element["id"]}`, { recursive: true });
223
+ }
224
+ }
225
+ } catch (e) {
226
+ this.log_translator("error", "try", `checkDeviceFolder: ${e}`);
227
+ }
228
+ }
229
+
230
+ async configcheck() {
231
+ try {
232
+ let isdecode = false;
233
+ // @ts-ignore
234
+ const adapterconfigs = this.adapterConfig;
235
+ if (adapterconfigs && adapterconfigs.native && adapterconfigs.native.hosts) {
236
+ for (const pw of adapterconfigs.native.hosts) {
237
+ if (pw.password != "" && pw.password.match(/<LUCKY-ESA>/gi) === null) {
238
+ pw.password = `<LUCKY-ESA>${this.encrypt(pw.password)}`;
239
+ isdecode = true;
240
+ }
241
+ }
242
+ }
243
+ if (isdecode) {
244
+ this.log_translator("info", "Encrypt");
245
+ if (adapterconfigs.native.hosts[0] === null) {
246
+ adapterconfigs.native.hosts = [];
247
+ }
248
+ this.updateConfig(adapterconfigs);
249
+ return true;
250
+ }
251
+ return false;
252
+ } catch (error) {
253
+ this.log_translator("error", "try", `configcheck: ${error}`);
254
+ }
255
+ }
256
+
257
+ async imap_connection(dev) {
258
+ if (this.clients[dev.user] != null) {
259
+ this.clients[dev.user].stop();
260
+ await this.sleep(1000);
261
+ this.clients[dev.user] = null;
262
+ }
263
+ this.restartIMAPConnection[dev.user] && this.clearTimeout(this.restartIMAPConnection[dev.user]);
264
+ this.clients[dev.user] = new MailListener(dev, this.log.debug);
265
+ this.clients[dev.user].start();
266
+ this.clients[dev.user].on("connected", (clientID) => {
267
+ this.log_translator("info", "connection", clientID);
268
+ ++this.countOnline;
269
+ if (this.countOnline === 1) {
270
+ this.setStateAsync("info.connection", true, true);
271
+ }
272
+ this.setState(`${clientID}.online`, {
273
+ val: true,
274
+ ack: true,
275
+ });
276
+ });
277
+
278
+ this.clients[dev.user].on("log", (level, text, info, clientID) => {
279
+ this.log_translator(level, text, info, clientID);
280
+ });
281
+
282
+ this.clients[dev.user].on("sub", (folder, clientID) => {
283
+ this.createinbox(clientID, folder);
284
+ this.log_translator("info", "INBOXFOLDER", clientID);
285
+ });
286
+
287
+ this.clients[dev.user].on("update", (log, seqno, info, clientID) => {
288
+ this.log_translator("info", log, clientID, JSON.stringify(info), seqno);
289
+ info["seqno"] = seqno;
290
+ this.setUpdate(clientID, info, "update");
291
+ });
292
+
293
+ this.clients[dev.user].on("status", (info, clientID) => {
294
+ this.log_translator("debug", "Status eMail folder", clientID, JSON.stringify(info));
295
+ this.setState(`${clientID}.total`, {
296
+ val: info.messages && info.messages.total != null ? info.messages.total : 0,
297
+ ack: true,
298
+ });
299
+ this.setState(`${clientID}.total_unread`, {
300
+ val: info.messages && info.messages.unseen != null ? info.messages.unseen : 0,
301
+ ack: true,
302
+ });
303
+ });
304
+
305
+ this.clients[dev.user].on("mailevent", (seqno, clientID) => {
306
+ this.log_translator("info", "Start new Mail", clientID, seqno);
307
+ this.setUpdate(clientID, { flags: [], new_mail: seqno }, "new mail");
308
+ this.setTotal(clientID, true, "total");
309
+ });
310
+
311
+ this.clients[dev.user].on("uidvalidity", (uidvalidity, clientID) => {
312
+ this.log_translator("info", "UID validity changes", clientID, uidvalidity);
313
+ });
314
+
315
+ this.clients[dev.user].on("expunge", (seqno, clientID) => {
316
+ this.log_translator("info", "EMail deleted", clientID, seqno);
317
+ this.setTotal(clientID, false, "total");
318
+ });
319
+
320
+ this.clients[dev.user].on("seqno", (seqno, clientID) => {
321
+ this.save_seqno[clientID] = seqno;
322
+ });
323
+
324
+ this.clients[dev.user].on("mailbox", (mailbox, clientID) => {
325
+ this.log_translator("debug", "mailbox", `${clientID} - ${JSON.stringify(mailbox)}`);
326
+ this.setState(`${clientID}.total`, {
327
+ val: mailbox.messages && mailbox.messages.total != null ? mailbox.messages.total : 0,
328
+ ack: true,
329
+ });
330
+ });
331
+
332
+ this.clients[dev.user].on("disconnected", (error, clientID) => {
333
+ error = !error ? "FALSE" : "TRUE";
334
+ this.log_translator("info", "disconnected", clientID, error);
335
+ this.log_translator("info", "Restart", clientID, 60);
336
+ this.restartIMAPConnection[clientID] = setTimeout(() => {
337
+ this.log_translator("info", "Restart now", clientID);
338
+ this.imap_connection(this.clientsRaw[clientID]);
339
+ }, 1000 * 60);
340
+ --this.countOnline;
341
+ if (this.countOnline === 0) {
342
+ this.setStateAsync("info.connection", true, true);
343
+ }
344
+ this.setState(`${clientID}.online`, {
345
+ val: false,
346
+ ack: true,
347
+ });
348
+ });
349
+
350
+ this.clients[dev.user].on("info", (info, clientID) => {
351
+ this.log_translator("info", "Info", clientID, tl.trans[info][this.lang]);
352
+ });
353
+
354
+ this.clients[dev.user].on("error", (err, clientID) => {
355
+ this.log_translator("error", "Error", clientID, err);
356
+ });
357
+
358
+ this.clients[dev.user].on("sendTo", (obj, results, clientID) => {
359
+ this.log_translator("info", "BLOCKLY value", clientID);
360
+ this.sendTo(obj.from, obj.command, results, obj.callback);
361
+ });
362
+
363
+ this.clients[dev.user].on("sendToError", (obj, results) => {
364
+ this.sendTo(obj.from, obj.command, this.helper_translator(results), obj.callback);
365
+ });
366
+
367
+ this.clients[dev.user].on("alert", (err, clientID) => {
368
+ this.log_translator("info", "Alert", clientID, err);
369
+ });
370
+ this.clients[dev.user].on("mail", (mail, seqno, attrs, info, clientID, count, all) => {
371
+ if (count < this.clientsRaw[clientID].maxi || this.clientsRaw[clientID].maxi == count) {
372
+ this.setStatesValue(mail, seqno, clientID, count, attrs);
373
+ }
374
+ if (count < this.clientsRaw[clientID].maxi_html || this.clientsRaw[clientID].maxi_html == count) {
375
+ this.createHTMLRows(mail, seqno, clientID, count, all, attrs);
376
+ }
377
+ this.log_translator(
378
+ "debug",
379
+ "Mail",
380
+ clientID,
381
+ `${JSON.stringify(mail)} Attributes: ${JSON.stringify(attrs)} Sequense: ${seqno} INFO: ${JSON.stringify(
382
+ info,
383
+ )}`,
384
+ );
385
+ //this.log_translator("debug", "Attributes", clientID, JSON.stringify(attrs));
386
+ //this.log_translator("debug", "Sequense", clientID, seqno);
387
+ //this.log_translator("debug", "Info", clientID, JSON.stringify(info));
388
+ });
389
+ }
390
+
391
+ async setTotal(clientID, event, dp) {
392
+ const actual = await this.getStateAsync(`${clientID}.${dp}`);
393
+ if (actual && actual["val"] != null) {
394
+ let total = Number(actual["val"]);
395
+ if (event) {
396
+ ++total;
397
+ } else {
398
+ --total;
399
+ }
400
+ this.setState(`${clientID}.${dp}`, {
401
+ val: total,
402
+ ack: true,
403
+ });
404
+ }
405
+ }
406
+
407
+ async setUpdate(clientID, info, trans) {
408
+ this.setState(`${clientID}.last_activity`, {
409
+ val: this.helper_translator(trans),
410
+ ack: true,
411
+ });
412
+ this.setState(`${clientID}.last_activity_json`, {
413
+ val: JSON.stringify(info),
414
+ ack: true,
415
+ });
416
+ this.setState(`${clientID}.last_activity_timestamp`, {
417
+ val: Date.now(),
418
+ ack: true,
419
+ });
420
+ }
421
+
422
+ async setStatesValue(mail, seqno, clientID, count, attrs) {
423
+ try {
424
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.subject`, {
425
+ val: mail.subject != null ? mail.subject : this.helper_translator("Unknown"),
426
+ ack: true,
427
+ });
428
+ //const receive_date = mail.date.toISOString().replace("T", " ").replace(/\..+/, "");
429
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.receive`, {
430
+ val: mail.date != null ? mail.date.toString() : this.helper_translator("Unknown"),
431
+ ack: true,
432
+ });
433
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.content`, {
434
+ val: mail.html != null && mail.html ? mail.html : this.helper_translator("Unknown"),
435
+ ack: true,
436
+ });
437
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.text`, {
438
+ val: mail.text != null ? mail.text : this.helper_translator("Unknown"),
439
+ ack: true,
440
+ });
441
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.texthtml`, {
442
+ val: mail.textAsHtml != null ? mail.textAsHtml : this.helper_translator("Unknown"),
443
+ ack: true,
444
+ });
445
+ let add = [];
446
+ if (mail.to && mail.to.value != null) {
447
+ for (const address of mail.to.value) {
448
+ if (address.address != null) {
449
+ add.push(address.address);
450
+ }
451
+ }
452
+ }
453
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.to`, {
454
+ val: JSON.stringify(add),
455
+ ack: true,
456
+ });
457
+ add = [];
458
+ if (mail.from && mail.from.value != null) {
459
+ for (const address of mail.from.value) {
460
+ if (address.address != null) {
461
+ add.push(address.address);
462
+ }
463
+ }
464
+ }
465
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.from`, {
466
+ val: JSON.stringify(add),
467
+ ack: true,
468
+ });
469
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.flag`, {
470
+ val: attrs.flags != null ? JSON.stringify(attrs.flags) : "",
471
+ ack: true,
472
+ });
473
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.uid`, {
474
+ val: attrs.uid != null ? attrs.uid : "",
475
+ ack: true,
476
+ });
477
+ await this.setStateAsync(`${clientID}.email.email_${("0" + count).slice(-2)}.seq`, {
478
+ val: seqno != null ? seqno : "",
479
+ ack: true,
480
+ });
481
+ } catch (e) {
482
+ this.log_translator("error", "try", `setStatesValue: ${e}`);
483
+ }
484
+ }
485
+
486
+ /**
487
+ * @param {ioBroker.Message} obj
488
+ */
489
+ async onMessage(obj) {
490
+ if (this.double_call[obj._id] != null) {
491
+ return;
492
+ }
493
+ this.double_call[obj._id] = true;
494
+ this.log_translator("debug", "Message", JSON.stringify(obj));
495
+ let adapterconfigs = {};
496
+ let _obj = {};
497
+ try {
498
+ // @ts-ignore
499
+ adapterconfigs = this.adapterConfig;
500
+ _obj = JSON.parse(JSON.stringify(obj));
501
+ } catch (error) {
502
+ this.log_translator("error", "catch", `onMessage: ${error}`);
503
+ this.sendTo(obj.from, obj.command, [], obj.callback);
504
+ delete this.double_call[obj._id];
505
+ return;
506
+ }
507
+ let icon_array = [];
508
+ const icons = [];
509
+ switch (obj.command) {
510
+ case "getIconList":
511
+ if (obj.callback) {
512
+ try {
513
+ if (_obj && _obj.message && _obj.message.icon && _obj.message.icon.icons) {
514
+ icon_array = _obj.message.icon.icons;
515
+ } else if (adapterconfigs && adapterconfigs.native && adapterconfigs.native.icons) {
516
+ icon_array = adapterconfigs.native.icons;
517
+ }
518
+ if (icon_array && Object.keys(icon_array).length > 0) {
519
+ for (const icon of icon_array) {
520
+ const label = icon.iconname;
521
+ icons.push({ label: label, value: icon.picture });
522
+ }
523
+ icons.sort((a, b) => (a.label > b.label ? 1 : b.label > a.label ? -1 : 0));
524
+ this.sendTo(obj.from, obj.command, icons, obj.callback);
525
+ } else {
526
+ this.sendTo(obj.from, obj.command, [], obj.callback);
527
+ }
528
+ } catch (error) {
529
+ delete this.double_call[obj._id];
530
+ this.log_translator("error", "catch", `onMessage: ${error}`);
531
+ this.sendTo(obj.from, obj.command, [], obj.callback);
532
+ }
533
+ }
534
+ delete this.double_call[obj._id];
535
+ break;
536
+ case "getBlockly":
537
+ if (
538
+ obj.message &&
539
+ obj.message["search"] != "" &&
540
+ obj.message["device"] != "" &&
541
+ obj.message["max"] > 0 &&
542
+ obj.message["max"] < 100
543
+ ) {
544
+ if (obj.message["device"] === "all") {
545
+ for (const dev in this.clientsRaw) {
546
+ const userdev = this.clientsRaw[dev];
547
+ if (userdev.activ) {
548
+ this.setStateSearch(userdev.user, obj.message["search"], obj.message["max"]);
549
+ }
550
+ }
551
+ } else {
552
+ const user = obj.message["device"].replace(FORBIDDEN_CHARS, "_");
553
+ if (this.clientsRaw[user]) {
554
+ if (this.clientsRaw[user].activ) {
555
+ this.setStateSearch(user, obj.message["search"], obj.message["max"]);
556
+ } else {
557
+ this.log_translator("info", "IMAP disabled", `${user} - ${obj.message["device"]}`);
558
+ }
559
+ } else {
560
+ this.log_translator("info", "not found imap", `${user} - ${obj.message["device"]}`);
561
+ }
562
+ }
563
+ }
564
+ break;
565
+ case "getIMAPRequest":
566
+ if (obj.callback) {
567
+ if (
568
+ obj.message &&
569
+ obj.message["search"] != "" &&
570
+ obj.message["name"] != "" &&
571
+ obj.message["bodie"] != "" &&
572
+ obj.message["parse"] != ""
573
+ ) {
574
+ if (obj.message["name"] !== "all") {
575
+ const user = obj.message["name"].replace(FORBIDDEN_CHARS, "_");
576
+ this.clients[user].custom_search(_obj);
577
+ } else {
578
+ this.log_translator("info", "No IMAP selected");
579
+ this.sendTo(obj.from, obj.command, [], obj.callback);
580
+ }
581
+ }
582
+ }
583
+ break;
584
+ case "getIMAPData":
585
+ if (obj.callback) {
586
+ if (obj.message && obj.message["name"] != "" && obj.message["value"] != "") {
587
+ if (obj.message["name"] !== "all") {
588
+ const user = obj.message["name"].replace(FORBIDDEN_CHARS, "_");
589
+ if (obj.message["value"] === "data") {
590
+ this.sendTo(obj.from, obj.command, this.save_json[user], obj.callback);
591
+ } else {
592
+ this.sendTo(obj.from, obj.command, this.save_seqno[user], obj.callback);
593
+ }
594
+ } else {
595
+ this.log_translator("info", "No IMAP selected");
596
+ this.sendTo(obj.from, obj.command, [], obj.callback);
597
+ }
598
+ }
599
+ }
600
+ break;
601
+ case "getFlags":
602
+ if (
603
+ obj.message &&
604
+ obj.message["flag"] != "" &&
605
+ obj.message["seqno"] > 0 &&
606
+ obj.message["name"] != "" &&
607
+ obj.message["flagtype"] != "" &&
608
+ obj.message["name"] !== "all"
609
+ ) {
610
+ this.log_translator(
611
+ "info",
612
+ "Set Flags",
613
+ obj.message["flag"],
614
+ obj.message["seqno"],
615
+ obj.message["flagtype"],
616
+ );
617
+ const user = obj.message["name"].replace(FORBIDDEN_CHARS, "_");
618
+ const flags = [];
619
+ for (const flag of obj.message["flagtype"].split(",")) {
620
+ flags.push("\\" + flag);
621
+ }
622
+ if (obj.message["flag"] === "addFlags" && typeof this.clients[user] === "object") {
623
+ this.clients[user].set_addFlags(obj.message["seqno"], _obj.message["flagtype"]);
624
+ } else if (obj.message["flag"] === "delFlags" && typeof this.clients[user] === "object") {
625
+ this.clients[user].set_delFlags(obj.message["seqno"], _obj.message["flagtype"]);
626
+ }
627
+ if (obj.message["flag"] === "setFlags" && typeof this.clients[user] === "object") {
628
+ this.clients[user].set_setFlags(obj.message["seqno"], flags);
629
+ }
630
+ }
631
+ break;
632
+ default:
633
+ this.sendTo(obj.from, obj.command, [], obj.callback);
634
+ delete this.double_call[obj._id];
635
+ }
636
+ }
637
+
638
+ async setStateSearch(id, user, max) {
639
+ await this.setStateAsync(`${id}.remote.criteria`, {
640
+ val: user,
641
+ ack: true,
642
+ });
643
+ await this.setStateAsync(`${id}.remote.show_mails`, {
644
+ val: max,
645
+ ack: true,
646
+ });
647
+ await this.setStateAsync(`${id}.remote.search_start`, {
648
+ val: true,
649
+ ack: false,
650
+ });
651
+ }
652
+
653
+ sleep(ms) {
654
+ return new Promise((resolve) => setTimeout(resolve, ms));
655
+ }
656
+
657
+ /**
658
+ * Is called when adapter shuts down - callback has to be called under any circumstances!
659
+ * @param {() => void} callback
660
+ */
661
+ onUnload(callback) {
662
+ try {
663
+ for (const dev of this.clientsID) {
664
+ this.clients[dev.user].stop();
665
+ this.restartIMAPConnection[dev] && this.clearTimeout(this.restartIMAPConnection[dev]);
666
+ }
667
+ this.qualityInterval && this.clearInterval(this.qualityInterval);
668
+ this.statusInterval && this.clearInterval(this.statusInterval);
669
+ this.sleepTimer && this.clearTimeout(this.sleepTimer);
670
+ callback();
671
+ } catch (e) {
672
+ callback();
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Is called if a subscribed state changes
678
+ * @param {string} id
679
+ * @param {ioBroker.State | null | undefined} state
680
+ */
681
+ async onStateChange(id, state) {
682
+ if (state && !state.ack) {
683
+ const command = id.split(".").pop();
684
+ const clientID = id.split(".")[2];
685
+ if (
686
+ this.clientsHTML[clientID] != null &&
687
+ command != null &&
688
+ this.clientsHTML[clientID][command] != null &&
689
+ state.val != null
690
+ ) {
691
+ this.log_translator("debug", "change_attribut", command, clientID);
692
+ this.clientsHTML[clientID][command] = state.val;
693
+ this.setAckFlag(id);
694
+ return;
695
+ }
696
+ if (command === "criteria") {
697
+ this.setAckFlag(id);
698
+ return;
699
+ }
700
+ if (command === "show_mails") {
701
+ this.setAckFlag(id);
702
+ return;
703
+ }
704
+ if (command === "search_start") {
705
+ const criteria = await this.getStateAsync(`${clientID}.remote.criteria`);
706
+ const show = await this.getStateAsync(`${clientID}.remote.show_mails`);
707
+ if (criteria && criteria.val && show && show.val != null) {
708
+ this.clientsRaw[clientID].flag = criteria.val;
709
+ this.clientsRaw[clientID].maxi_html = show.val;
710
+ this.imap_connection(this.clientsRaw[clientID]);
711
+ }
712
+ this.setAckFlag(id, { val: false });
713
+ return;
714
+ }
715
+ if (command === "change_folder" && state.val != "") {
716
+ if (this.clients[clientID]) {
717
+ this.clients[clientID].onReady(state.val);
718
+ this.setState(`${clientID}.active_inbox`, {
719
+ val: state.val,
720
+ ack: true,
721
+ });
722
+ this.setAckFlag(id);
723
+ }
724
+ return;
725
+ }
726
+ if (command === "reload_emails") {
727
+ if (this.clients[clientID]) {
728
+ this.clients[clientID].onNewRead();
729
+ this.setAckFlag(id, { val: false });
730
+ }
731
+ return;
732
+ }
733
+ }
734
+ }
735
+
736
+ async setAckFlag(id, value) {
737
+ try {
738
+ if (id) {
739
+ this.setState(id, {
740
+ ack: true,
741
+ ...value,
742
+ });
743
+ }
744
+ } catch (e) {
745
+ this.log_translator("error", "try", `setAckFlag: ${e}`);
746
+ }
747
+ }
748
+
749
+ log_translator(level, text, merge_array, merge_array2, merge_array3) {
750
+ try {
751
+ const loglevel = !!this.log[level];
752
+ if (loglevel && level != "debug") {
753
+ //if (loglevel) {
754
+ if (tl.trans[text] != null) {
755
+ if (merge_array3) {
756
+ this.log[level](format(tl.trans[text][this.lang], merge_array, merge_array2, merge_array3));
757
+ } else if (merge_array2) {
758
+ this.log[level](format(tl.trans[text][this.lang], merge_array, merge_array2));
759
+ } else if (merge_array) {
760
+ this.log[level](format(tl.trans[text][this.lang], merge_array));
761
+ } else {
762
+ this.log[level](tl.trans[text][this.lang]);
763
+ }
764
+ } else {
765
+ this.log.warn(format(tl.trans["Cannot find translation"][this.lang], text));
766
+ }
767
+ }
768
+ } catch (e) {
769
+ this.log.error("try log_translator: " + e + " - " + text);
770
+ }
771
+ }
772
+
773
+ helper_translator(text, merge_array, merge_array1) {
774
+ try {
775
+ if (tl.trans[text][this.lang]) {
776
+ if (merge_array1) {
777
+ return format(tl.trans[text][this.lang], merge_array, merge_array1);
778
+ } else if (merge_array) {
779
+ return format(tl.trans[text][this.lang], merge_array);
780
+ } else {
781
+ return tl.trans[text][this.lang];
782
+ }
783
+ } else {
784
+ return tl.trans["Unknown"][this.lang];
785
+ }
786
+ } catch (e) {
787
+ this.log.error("try helper_translator: " + e);
788
+ }
789
+ }
790
+
791
+ async createHTMLRows(mail, seqno, clientID, count, all, attrs) {
792
+ if (count == 1) {
793
+ this.clientsRows[clientID] = "";
794
+ this.save_json[clientID] = [];
795
+ }
796
+ const id = this.clientsHTML[clientID];
797
+ mail.seqno = seqno;
798
+ mail.attrs = attrs;
799
+ this.save_json[clientID].push(mail);
800
+ const isEven = count % 2 != 0 ? id["mails_even_color"] : id["mails_odd_color"];
801
+ const isToday = (someDate) => {
802
+ const today = new Date();
803
+ return (
804
+ someDate.getDate() == today.getDate() &&
805
+ someDate.getMonth() == today.getMonth() &&
806
+ someDate.getFullYear() == today.getFullYear()
807
+ );
808
+ };
809
+ let days;
810
+ if (isToday(new Date(mail.date))) {
811
+ days = count % 2 != 0 ? id["mails_today_color"] : id["mails_today_color_odd"];
812
+ } else {
813
+ days = count % 2 != 0 ? id["mails_nextday_color_even"] : id["mails_nextday_color_odd"];
814
+ }
815
+ const weight = attrs.flags != "" ? "normal" : "bold";
816
+ let from = this.helper_translator("Unknown");
817
+ let org_from = this.helper_translator("Unknown");
818
+ if (mail.from && mail.from.value && mail.from.value[0].name != null && mail.from.value[0].name != "") {
819
+ from = mail.from.value[0].name;
820
+ org_from = mail.from.value[0].address;
821
+ } else if (
822
+ mail.from &&
823
+ mail.from.value &&
824
+ mail.from.value[0].address != null &&
825
+ mail.from.value[0].address != ""
826
+ ) {
827
+ from = mail.from.value[0].address;
828
+ org_from = mail.from.value[0].address;
829
+ }
830
+ const org_subject = mail.subject;
831
+ let subject = mail.subject;
832
+ let content = mail.text != null ? mail.text : convert(mail.html);
833
+ if (id["choose_content"] == "html") {
834
+ content = mail.html != null ? mail.html : "";
835
+ } else if (id["choose_content"] == "text") {
836
+ content = mail.text != null ? mail.text : convert(mail.html);
837
+ } else if (id["choose_content"] == "textAsHtml") {
838
+ content = mail.textAsHtml != null ? mail.textAsHtml : mail.text;
839
+ } else if (id["choose_content"] == "html_convert") {
840
+ content = mail.html != null ? convert(mail.html) : mail.text;
841
+ } else if (id["choose_content"] == "textAsHtml_convert") {
842
+ content = mail.textAsHtml != null ? convert(mail.textAsHtml) : mail.text;
843
+ }
844
+ let org_content = content;
845
+ if (org_content != null && org_content != "") {
846
+ org_content = org_content
847
+ .toString()
848
+ .replace(/"/g, "'")
849
+ .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, "");
850
+ }
851
+ if (mail.subject && mail.subject.toString().length > id["short_subject"] && id["short_subject"] > 0) {
852
+ subject = mail.subject.substring(0, id["short_subject"]);
853
+ }
854
+ if (content && content.toString().length > id["short_content"] && id["short_content"] > 0) {
855
+ content = content.substring(0, id["short_content"]);
856
+ }
857
+ attrs.flags = attrs.flags != "" ? attrs.flags.toString().replace(/\\/, "") : "unseen";
858
+ this.clientsRows[clientID] += `
859
+ <tr style="background-color:${isEven};
860
+ color:${days};
861
+ font-weight:${weight};
862
+ font-size:${id["header_font_size"]}px;">
863
+ <td style="text-align:${id["headline_align_column_1"]}">${count}</td>
864
+ <td title="${org_from}" style="text-align:${id["headline_align_column_2"]}">${from}</td>
865
+ <td title="${org_subject}" style="text-align:${id["headline_align_column_3"]}">${subject}</td>
866
+ <td style="text-align:${id["headline_align_column_4"]}">
867
+ ${this.formatDate(new Date(mail.date).getTime(), "TT.MM.JJ - SS:mm")}</td>
868
+ <td title="${org_content}" style="text-align:${id["headline_align_column_5"]}">${content}</td>
869
+ <td style="text-align:${id["headline_align_column_6"]}">${seqno}</td>
870
+ <td style="text-align:${id["headline_align_column_7"]}">${attrs.flags}</td>
871
+ </tr>`;
872
+ if (count == all || this.clientsRaw[clientID].maxi_html == count) {
873
+ await this.createHTML(clientID, this.clientsRows[clientID], count, all);
874
+ this.clientsRows[clientID] = "";
875
+ }
876
+ }
877
+
878
+ async createHTML(ident, htmltext, count, all) {
879
+ try {
880
+ const id = this.clientsHTML[ident];
881
+ let div = '<div class="container">';
882
+ let div_css = `
883
+ div.container {
884
+ align-items: center;
885
+ justify-content: center
886
+ }`;
887
+ if (id["jarvis"]) {
888
+ div = "<div>";
889
+ div_css = "";
890
+ }
891
+ const htmlStart = `
892
+ <!DOCTYPE html>
893
+ <html lang="${this.lang}">
894
+ <head>
895
+ <title>IMAP</title>
896
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
897
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
898
+ <style>
899
+ * {
900
+ margin: 0;
901
+ }
902
+ body {
903
+ background-color: ${id["body_background"]}; margin: 0 auto;
904
+ }
905
+ p {
906
+ padding-top: 10px; padding-bottom: 10px; text-align: ${id["p_tag_text_align"]}
907
+ }
908
+ td {
909
+ padding:${id["td_tag_cell"]}px; border:0px solid ${id["td_tag_border_color"]};
910
+ border-right:${id["td_tag_border_right"]}px solid ${id["td_tag_border_color"]};
911
+ border-bottom:${id["td_tag_border_bottom"]}px solid ${id["td_tag_border_color"]};
912
+ }
913
+ table {
914
+ width: ${id["table_tag_width"]};
915
+ margin: ${id["table_tag_text_align"]};
916
+ border:1px solid ${id["table_tag_border_color"]};
917
+ border-spacing: ${id["table_tag_cell"]}px;
918
+ border-collapse: collapse;
919
+ }
920
+ td:nth-child(1) {
921
+ width: ${id["td_tag_2_colums"]}
922
+ }
923
+ td:nth-child(2) {
924
+ width:${id["td_tag_2_colums"]}
925
+ }
926
+ ${div_css}
927
+ thread {
928
+ display: table-header-group
929
+ }
930
+ tbody {
931
+ display: table-row-group
932
+ }
933
+ tfoot {
934
+ display: table-footer-group
935
+ }
936
+ </style>
937
+ </head>
938
+ <body>
939
+ ${div}
940
+ <table style="width:${id["header_width"]};
941
+ border:${id["header_border"]}px; border-color:${id["header_tag_border_color"]};
942
+ color:${id["header_text_color"]}; font-size:${id["header_font_size"]}px;
943
+ font-family:${id["header_font"]};
944
+ background-image: linear-gradient(42deg,${id["header_linear_color_2"]},
945
+ ${id["header_linear_color_1"]});">
946
+ <thead>
947
+ <tr>
948
+ <th colspan="7" scope="colgroup">
949
+ <p style="color:${id["top_text_color"]}; font-family:${id["top_font"]};
950
+ font-size:${id["top_font_size"]}px; font-weight:${id["top_font_weight"]}">
951
+ ${id["top_text"]}&ensp;&ensp;${this.helper_translator("top_last_update")}
952
+ ${this.formatDate(new Date(), "TT.MM.JJJJ hh:mm:ss")}</p></th>
953
+ </tr>
954
+ <tr style="color:${id["headline_color"]}; height:${id["headline_height"]}px;
955
+ font-size: ${id["headline_font_size"]}px; font-weight: ${id["headline_style"]};
956
+ border-bottom: ${id["headline_underlined"]}px solid ${id["headline_underlined_color"]}">
957
+ <th style="text-align:${id["headline_align_column_1"]}; width:${id["headline_column_width_1"]}">
958
+ &ensp;${this.helper_translator("ID")}&ensp;
959
+ </th>
960
+ <th style="text-align:${id["headline_align_column_2"]}; width:${id["headline_column_width_2"]}">
961
+ &ensp;${this.helper_translator("Header_From")}&ensp;
962
+ </th>
963
+ <th style="text-align:${id["headline_align_column_3"]}; width:${id["headline_column_width_3"]}">
964
+ &ensp;${this.helper_translator("Header_Subject")}&ensp;
965
+ </th>
966
+ <th style="text-align:${id["headline_align_column_4"]}; width:${id["headline_column_width_4"]}">
967
+ &ensp;${this.helper_translator("Header_Date")}&ensp;
968
+ </th>
969
+ <th style="text-align:${id["headline_align_column_5"]}; width:${id["headline_column_width_5"]}">
970
+ &ensp;${this.helper_translator("Header_Content")}&ensp;
971
+ </th>
972
+ <th style="text-align:${id["headline_align_column_6"]}; width:${id["headline_column_width_6"]}">
973
+ &ensp;${this.helper_translator("Header_SEQ")}&ensp;
974
+ </th>
975
+ <th style="text-align:${id["headline_align_column_7"]}; width:${id["headline_column_width_7"]}">
976
+ &ensp;${this.helper_translator("Flag")}&ensp;
977
+ </th>
978
+ </tr>
979
+ </thead>
980
+ <tfoot>
981
+ <tr>
982
+ <th colspan="7" scope="colgroup">
983
+ <p style="color:${id["top_text_color"]}; font-family:${id["top_font"]};
984
+ font-size:${id["top_font_size"]}px; font-weight:${id["top_font_weight"]}">
985
+ ${this.helper_translator("footer", count, all)}</p></th>
986
+ </tr>
987
+ </tfoot>
988
+ <tbody>
989
+ ${htmltext}
990
+ </tbody>
991
+ `;
992
+ const htmlEnd = `</table></div></body></html>`;
993
+ await this.setStateAsync(`${ident}.html`, {
994
+ val: htmlStart + htmlEnd,
995
+ ack: true,
996
+ });
997
+ this.json_table(this.save_json[ident], ident);
998
+ } catch (e) {
999
+ this.log_translator("error", "try", `createHTML: ${e}`);
1000
+ }
1001
+ }
1002
+
1003
+ async json_table(jsons, id) {
1004
+ try {
1005
+ if (typeof jsons != "object") {
1006
+ return;
1007
+ }
1008
+ let count = 0;
1009
+ const new_array = [];
1010
+ let new_json = {};
1011
+ let addaddress = [];
1012
+ let addname = [];
1013
+ for (const element of jsons) {
1014
+ new_json = {};
1015
+ new_json["id"] = ++count;
1016
+ new_json["date"] = element.date ? this.formatDate(element.date, "TT.MM.JJJJ hh:mm:ss") : "";
1017
+ addaddress = [];
1018
+ addname = [];
1019
+ if (element.from && element.from.value != null) {
1020
+ for (const address of element.from.value) {
1021
+ if (address.address != null) {
1022
+ addaddress.push(address.address);
1023
+ addname.push(address.name);
1024
+ }
1025
+ }
1026
+ }
1027
+ new_json["from"] = addaddress;
1028
+ new_json["from_name"] = addname;
1029
+ addaddress = [];
1030
+ addname = [];
1031
+ if (element.to && element.to.value != null) {
1032
+ for (const address of element.to.value) {
1033
+ if (address.address != null) {
1034
+ addaddress.push(address.address);
1035
+ addname.push(address.name);
1036
+ }
1037
+ }
1038
+ }
1039
+ new_json["to"] = addaddress;
1040
+ new_json["to_name"] = addname;
1041
+ new_json["subject"] = element.subject ? element.subject : "";
1042
+ new_json["text"] = element.text ? element.text : "";
1043
+ new_json["html"] = element.html ? element.html : "";
1044
+ new_json["textAsHtml"] = element.textAsHtml ? element.textAsHtml : "";
1045
+ new_json["seqno"] = element.seqno ? element.seqno : "";
1046
+ new_json["flag"] = element.attrs.flags ? element.attrs.flags : "";
1047
+ new_array.push(new_json);
1048
+ }
1049
+ await this.setStateAsync(`${id}.json`, {
1050
+ val: JSON.stringify(new_array),
1051
+ ack: true,
1052
+ });
1053
+ } catch (e) {
1054
+ this.log_translator("error", "try", `json_table: ${e}`);
1055
+ }
1056
+ }
1057
+
1058
+ async cleanupQuality() {
1059
+ this.log_translator("debug", "Data point quality is cleaned up");
1060
+ const quality = {
1061
+ 0: "0x00 - good",
1062
+ 1: "0x01 - general problem",
1063
+ 2: "0x02 - no connection problem",
1064
+ 16: "0x10 - substitute value from controller",
1065
+ 17: "0x11 - general problem by instance",
1066
+ 18: "0x12 - instance not connected",
1067
+ 32: "0x20 - substitute initial value",
1068
+ 64: "0x40 - substitute value from device or instance",
1069
+ 65: "0x41 - general problem by device",
1070
+ 66: "0x42 - device not connected",
1071
+ 68: "0x44 - device reports error",
1072
+ 128: "0x80 - substitute value from sensor",
1073
+ 129: "0x81 - general problem by sensor",
1074
+ 130: "0x82 - sensor not connected",
1075
+ 132: "0x84 - sensor reports error",
1076
+ };
1077
+ try {
1078
+ for (const deviceId of this.clientsID) {
1079
+ const all_dp = await this.getObjectListAsync({
1080
+ startkey: `${this.namespace}.${deviceId}.`,
1081
+ endkey: `${this.namespace}.${deviceId}.\u9999`,
1082
+ });
1083
+ const dp_array = [];
1084
+ if (all_dp && all_dp.rows) {
1085
+ let role;
1086
+ for (const dp of all_dp.rows) {
1087
+ if (dp.value.type === "state") {
1088
+ const states = await this.getStateAsync(dp.id);
1089
+ if (states && states.q != null && states.q != 0) {
1090
+ this.log_translator("debug", "Datapoint", `${dp.id} - ${JSON.stringify(states)}`);
1091
+ if (quality[states.q]) {
1092
+ const isfind = dp_array.find((mes) => mes.message === quality[states.q]);
1093
+ if (isfind) {
1094
+ this.log_translator("debug", "Found", JSON.stringify(isfind));
1095
+ ++isfind.counter;
1096
+ isfind.dp[isfind.counter] = dp.id;
1097
+ } else {
1098
+ this.log_translator("debug", "Found", JSON.stringify(isfind));
1099
+ const new_array = {
1100
+ message: quality[states.q],
1101
+ quality: states.q,
1102
+ counter: 1,
1103
+ dp: { 1: dp.id },
1104
+ };
1105
+ dp_array.push(new_array);
1106
+ }
1107
+ if (dp.value.common.role.toString().match(/button/gi) != null) {
1108
+ role = { val: false };
1109
+ } else {
1110
+ role = null;
1111
+ }
1112
+ if (quality[states.q] === "0x20 - substitute initial value") {
1113
+ await this.setStateAsync(`${dp.id}`, {
1114
+ ack: true,
1115
+ ...role,
1116
+ });
1117
+ }
1118
+ } else {
1119
+ this.log_translator("debug", "Missing quality", states.q);
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ await this.setStateAsync(`${deviceId}.quality`, {
1126
+ val:
1127
+ Object.keys(dp_array).length > 0
1128
+ ? JSON.stringify(dp_array)
1129
+ : JSON.stringify({ message: "No Message" }),
1130
+ ack: true,
1131
+ });
1132
+ }
1133
+ } catch (e) {
1134
+ this.log_translator("error", "try", `cleanupQuality: ${e}`);
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ if (require.main !== module) {
1140
+ // Export the constructor in compact mode
1141
+ /**
1142
+ * @param {Partial<utils.AdapterOptions>} [options={}]
1143
+ */
1144
+ module.exports = (options) => new Imap(options);
1145
+ } else {
1146
+ // otherwise start the instance directly
1147
+ new Imap();
1148
+ }