@vielhuber/wahelper 1.8.5 → 1.8.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielhuber/wahelper",
3
- "version": "1.8.5",
3
+ "version": "1.8.7",
4
4
  "description": "Lightweight whatsapp integration layer.",
5
5
  "main": "wahelper.js",
6
6
  "files": [
@@ -276,6 +276,11 @@ export default class wahelperDaemon {
276
276
  this.log('storeDataToDatabase');
277
277
 
278
278
  let messages = Array.isArray(data?.messages) ? data.messages : [];
279
+ if (Array.isArray(data?.updates)) {
280
+ messages = data.updates
281
+ .filter(({ update }) => update?.message?.editedMessage)
282
+ .map(({ key, update }) => ({ ...update, key }));
283
+ }
279
284
  let chats = Array.isArray(data) ? data : Array.isArray(data?.chats) ? data.chats : [];
280
285
  if (messages.length === 0 && chats.length === 0) {
281
286
  return;
@@ -301,14 +306,24 @@ export default class wahelperDaemon {
301
306
  let query = this.db.prepare(`
302
307
  INSERT OR IGNORE INTO messages
303
308
  (id, \`from\`, \`to\`, content, media_data, media_filename, timestamp)
304
- VALUES (?, ?, ?, ?, ?, ?, ?)
309
+ VALUES ($id, $from, $to, $content, $mediaData, $mediaFilename, $timestamp)
310
+ ON CONFLICT(id) DO UPDATE SET
311
+ \`from\` = excluded.\`from\`,
312
+ \`to\` = excluded.\`to\`,
313
+ content = CASE WHEN $preserveContent THEN messages.content ELSE excluded.content END,
314
+ media_data = COALESCE(excluded.media_data, messages.media_data),
315
+ media_filename = COALESCE(excluded.media_filename, messages.media_filename),
316
+ timestamp = CASE WHEN $preserveTimestamp THEN messages.timestamp ELSE excluded.timestamp END
305
317
  `);
306
318
 
307
319
  for (let messages__value of messages) {
308
320
  let id = messages__value.key?.id,
309
321
  chatId = messages__value.key?.remoteJid,
310
322
  fromMe = messages__value.key?.fromMe ? 1 : 0,
311
- timestamp = messages__value.messageTimestamp;
323
+ timestamp = Number(messages__value.messageTimestamp ?? NaN);
324
+ let isEdited = messages__value.message?.editedMessage !== undefined;
325
+ // Edit events carry the edit time, not the original send time.
326
+ let preserveTimestamp = isEdited || isNaN(timestamp);
312
327
 
313
328
  // status posts and newsletter items are not conversations. they carry
314
329
  // their author in remoteJidAlt, so without this they would be stored
@@ -317,12 +332,7 @@ export default class wahelperDaemon {
317
332
  continue;
318
333
  }
319
334
 
320
- if (timestamp !== undefined && timestamp !== null) {
321
- timestamp = Number(timestamp);
322
- if (isNaN(timestamp)) {
323
- timestamp = Math.floor(Date.now() / 1000);
324
- }
325
- } else {
335
+ if (isNaN(timestamp)) {
326
336
  timestamp = Math.floor(Date.now() / 1000);
327
337
  }
328
338
 
@@ -365,6 +375,7 @@ export default class wahelperDaemon {
365
375
  }
366
376
 
367
377
  let content = null,
378
+ preserveContent = false,
368
379
  mediaFilename = null,
369
380
  mediaData = null,
370
381
  mediaBufferInput = null;
@@ -484,8 +495,9 @@ export default class wahelperDaemon {
484
495
  }
485
496
 
486
497
  // skip media download on first run (initial history sync after fresh pairing)
487
- if (this.isFirstRun) {
498
+ if (this.isFirstRun && !isEdited) {
488
499
  if (content === null || content === '') {
500
+ preserveContent = true;
489
501
  content = '[Media message not downloaded on first run]';
490
502
  }
491
503
  mediaFilename = null;
@@ -506,12 +518,21 @@ export default class wahelperDaemon {
506
518
  mediaData = buffer.toString('base64');
507
519
  this.log('✅ Downloaded media ' + mediaFilename);
508
520
  } catch (error) {
509
- mediaData = mediaBufferInput?.url || null;
510
- this.log('⚠️ Failed to download media: ' + error.message + '. Storing URL instead.');
521
+ this.log('⚠️ Failed to download media: ' + error.message + '. Leaving media data empty.');
511
522
  }
512
523
  }
513
524
 
514
- query.run(id, from, to, content, mediaData, mediaFilename, timestamp);
525
+ query.run({
526
+ id,
527
+ from,
528
+ to,
529
+ content,
530
+ mediaData,
531
+ mediaFilename,
532
+ timestamp,
533
+ preserveContent: Number(preserveContent),
534
+ preserveTimestamp: Number(preserveTimestamp)
535
+ });
515
536
  count++;
516
537
 
517
538
  if (length < 100 || count % 100 === 0) {
@@ -526,11 +547,11 @@ export default class wahelperDaemon {
526
547
  this.db.exec('COMMIT');
527
548
  this.log('END TRANSACTION');
528
549
  if (count > 0) {
529
- this.log('Stored ' + count + ' new messages to database (' + length + ' total received)');
550
+ this.log('Stored ' + count + ' messages to database (' + length + ' total received)');
530
551
  process.stdout.write(
531
552
  '\r✅ Stored ' +
532
553
  count +
533
- ' new messages to database (' +
554
+ ' messages to database (' +
534
555
  length +
535
556
  ' total received)' +
536
557
  ' '.repeat(10) +
@@ -830,6 +851,7 @@ export default class wahelperDaemon {
830
851
 
831
852
  socket.ev.on('messages.update', async updates => {
832
853
  try {
854
+ await this.storeDataToDatabase({ updates });
833
855
  await this.markMessagesRead(updates);
834
856
  } catch (error) {
835
857
  this.log('⛔ messages.update handler failed: ' + error.message);
@@ -1173,7 +1195,12 @@ export default class wahelperDaemon {
1173
1195
  }
1174
1196
  }
1175
1197
 
1176
- if (process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
1198
+ // Importing the class must not start the daemon or connect to WhatsApp.
1199
+ if (
1200
+ process.argv[1] &&
1201
+ fs.existsSync(process.argv[1]) &&
1202
+ fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
1203
+ ) {
1177
1204
  let daemon = new wahelperDaemon();
1178
1205
  daemon.init();
1179
1206
  }
package/wahelper.js CHANGED
@@ -26,7 +26,11 @@ export default class wahelper {
26
26
  this.authFolder = 'auth_' + this.formatNumber(this.args.device);
27
27
  this.dbPath = 'whatsapp_' + this.formatNumber(this.args.device) + '.sqlite';
28
28
  this.logPath = 'whatsapp_' + this.formatNumber(this.args.device) + '.log';
29
- this.dataPath = 'whatsapp_' + this.formatNumber(this.args.device) + '.json';
29
+ if (this.args.request_id !== undefined && !/^[a-f0-9]{32}$/.test(this.args.request_id)) {
30
+ throw new Error('Invalid request id');
31
+ }
32
+ let requestSuffix = this.args.request_id === undefined ? '' : '_' + this.args.request_id;
33
+ this.dataPath = 'whatsapp_' + this.formatNumber(this.args.device) + requestSuffix + '.json';
30
34
  this.port = this.computePort(this.formatNumber(this.args.device));
31
35
  this.authToken = this.getAuthToken();
32
36
  }
@@ -199,6 +203,7 @@ export default class wahelper {
199
203
  };
200
204
  } catch (error) {
201
205
  this.log('⛔ Error fetching database: ' + error.message + ' (code: ' + error.code + ')');
206
+ this.write({ success: false, message: 'error', data: null }, true);
202
207
  }
203
208
  return null;
204
209
  }
@@ -269,6 +274,7 @@ export default class wahelper {
269
274
  };
270
275
  } catch (error) {
271
276
  this.log('⛔ Error fetching message from database: ' + error.message + ' (code: ' + error.code + ')');
277
+ this.write({ success: false, message: 'error', data: null }, true);
272
278
  }
273
279
  return null;
274
280
  }
@@ -576,7 +582,14 @@ export default class wahelper {
576
582
  this.writeOnEnd = msg;
577
583
  return;
578
584
  }
579
- fs.writeFileSync(this.dirname + '/' + this.dataPath, JSON.stringify(msg));
585
+ let responsePath = this.dirname + '/' + this.dataPath;
586
+ let temporaryPath = responsePath + '.' + crypto.randomUUID() + '.tmp';
587
+ try {
588
+ fs.writeFileSync(temporaryPath, JSON.stringify(msg), { flag: 'wx' });
589
+ fs.renameSync(temporaryPath, responsePath);
590
+ } finally {
591
+ fs.rmSync(temporaryPath, { force: true });
592
+ }
580
593
  }
581
594
 
582
595
  initDatabase() {
@@ -621,7 +634,14 @@ export default class wahelper {
621
634
  }
622
635
  }
623
636
 
624
- let wa = new wahelper();
625
- wa.init()
626
- .then(() => process.exit(0))
627
- .catch(() => process.exit(1));
637
+ // Importing the class must not start the CLI or terminate the caller.
638
+ if (
639
+ process.argv[1] &&
640
+ fs.existsSync(process.argv[1]) &&
641
+ fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
642
+ ) {
643
+ let wa = new wahelper();
644
+ wa.init()
645
+ .then(() => process.exit(0))
646
+ .catch(() => process.exit(1));
647
+ }