@vielhuber/wahelper 1.5.7 → 1.5.9

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/README.md CHANGED
@@ -55,6 +55,10 @@ npx wahelper \
55
55
  --action "fetch_messages" \
56
56
  --limit 42
57
57
 
58
+ # view a single message by id
59
+ --action "view_message" \
60
+ --id "ABCDEF1234567890"
61
+
58
62
  # send message to user
59
63
  --action "send_user" \
60
64
  --number "xxxxxxxxxxxx" \
@@ -79,6 +83,9 @@ $wahelper = new wahelper();
79
83
  // fetch messages
80
84
  $wahelper->fetchMessages(device: 'xxxxxxxxxxxx', limit: 42);
81
85
 
86
+ // view a single message by id
87
+ $wahelper->viewMessage(device: 'xxxxxxxxxxxx', id: 'ABCDEF1234567890');
88
+
82
89
  // send message to user
83
90
  $wahelper->sendUser(
84
91
  device: 'xxxxxxxxxxxx',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielhuber/wahelper",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
4
4
  "description": "Lightweight whatsapp integration layer.",
5
5
  "main": "wahelper.js",
6
6
  "files": [
@@ -129,9 +129,14 @@ export default class wahelperDaemon {
129
129
  content TEXT,
130
130
  media_data TEXT,
131
131
  media_filename TEXT,
132
- timestamp INTEGER
132
+ timestamp INTEGER,
133
+ \`read\` INTEGER NOT NULL DEFAULT 0
133
134
  );
134
135
  `);
136
+ let cols = this.db.prepare('PRAGMA table_info(messages)').all();
137
+ if (!cols.some(c => c.name === 'read')) {
138
+ this.db.exec('ALTER TABLE messages ADD COLUMN `read` INTEGER NOT NULL DEFAULT 0');
139
+ }
135
140
  } catch (error) {
136
141
  this.log('⛔ Error initing database: ' + error.message + ' (code: ' + error.code + ')');
137
142
  }
@@ -394,6 +399,42 @@ export default class wahelperDaemon {
394
399
  this.dbLock = false;
395
400
  }
396
401
 
402
+ async markMessagesRead(updates) {
403
+ if (!Array.isArray(updates) || updates.length === 0) {
404
+ return;
405
+ }
406
+ // WAMessageStatus.READ === 4
407
+ let ids = updates
408
+ .filter(u => u?.update?.status === 4)
409
+ .map(u => u?.key?.id)
410
+ .filter(id => typeof id === 'string' && id.length > 0);
411
+ if (ids.length === 0) {
412
+ return;
413
+ }
414
+ while (this.dbLock) {
415
+ await new Promise(resolve => setTimeout(resolve, 100));
416
+ }
417
+ this.dbLock = true;
418
+ try {
419
+ if (this.dbIsOpen === false) {
420
+ this.initDatabase();
421
+ }
422
+ let stmt = this.db.prepare('UPDATE messages SET `read` = 1 WHERE id = ? AND `read` = 0');
423
+ let touched = 0;
424
+ for (let id of ids) {
425
+ let res = stmt.run(id);
426
+ if (res && res.changes > 0) touched++;
427
+ }
428
+ if (touched > 0) {
429
+ this.log('marked ' + touched + '/' + ids.length + ' messages as read');
430
+ }
431
+ } catch (error) {
432
+ this.log('⛔ markMessagesRead failed: ' + error.message);
433
+ } finally {
434
+ this.dbLock = false;
435
+ }
436
+ }
437
+
397
438
  async sendMessageToUser(number = null, message = null, attachments = null) {
398
439
  if (!this.connected || !this.sock) {
399
440
  throw new Error('not_connected');
@@ -502,6 +543,14 @@ export default class wahelperDaemon {
502
543
  await this.storeDataToDatabase(obj);
503
544
  });
504
545
 
546
+ this.sock.ev.on('messages.update', async updates => {
547
+ try {
548
+ await this.markMessagesRead(updates);
549
+ } catch (error) {
550
+ this.log('⛔ messages.update handler failed: ' + error.message);
551
+ }
552
+ });
553
+
505
554
  this.sock.ev.on('creds.update', saveCreds);
506
555
 
507
556
  this.sock.ev.on('connection.update', async update => {
package/wahelper.js CHANGED
@@ -55,10 +55,11 @@ export default class wahelper {
55
55
  this.args.device === undefined ||
56
56
  (this.args.action === 'send_user' && (this.args.number === undefined || this.args.message === undefined)) ||
57
57
  (this.args.action === 'send_group' && (this.args.name === undefined || this.args.message === undefined)) ||
58
+ (this.args.action === 'view_message' && (this.args.id === undefined || this.args.id === '')) ||
58
59
  (this.args.action === 'fetch_messages' &&
59
60
  this.args.limit !== undefined &&
60
61
  typeof this.args.limit !== 'number') ||
61
- !['fetch_messages', 'send_user', 'send_group'].includes(this.args.action)
62
+ !['fetch_messages', 'view_message', 'send_user', 'send_group'].includes(this.args.action)
62
63
  ) {
63
64
  console.error('input missing or unknown action!');
64
65
  this.log('⛔input missing or unknown action!');
@@ -106,6 +107,9 @@ export default class wahelper {
106
107
  if (this.args.action === 'fetch_messages') {
107
108
  response = await this.fetchMessages(this.args.limit);
108
109
  }
110
+ if (this.args.action === 'view_message') {
111
+ response = await this.viewMessage(this.args.id);
112
+ }
109
113
  if (this.args.action === 'send_user') {
110
114
  response = await this.sendMessageToUser(this.args.number, this.args.message, this.args.attachments);
111
115
  }
@@ -129,7 +133,7 @@ export default class wahelper {
129
133
  let messages = this.db
130
134
  .prepare(
131
135
  `
132
- SELECT id, \`from\`, \`to\`, content, media_filename, timestamp
136
+ SELECT id, \`from\`, \`to\`, content, media_filename, timestamp, \`read\`
133
137
  FROM messages
134
138
  ORDER BY timestamp DESC
135
139
  ${limit !== null ? 'LIMIT ' + limit : ''}
@@ -155,6 +159,37 @@ export default class wahelper {
155
159
  return null;
156
160
  }
157
161
 
162
+ async viewMessage(id = null) {
163
+ // lookup directly from database — no connection to daemon needed
164
+ try {
165
+ let message =
166
+ this.db
167
+ .prepare(
168
+ `
169
+ SELECT id, \`from\`, \`to\`, content, media_filename, timestamp, \`read\`
170
+ FROM messages
171
+ WHERE id = ?
172
+ LIMIT 1
173
+ `
174
+ )
175
+ .get(id) || null;
176
+ if (message === null) {
177
+ console.log('Message not found: ' + id);
178
+ this.write({ success: false, message: 'message_not_found', data: null }, true);
179
+ return { content: [{ type: 'text', text: 'Message not found: ' + id }], structuredContent: null };
180
+ }
181
+ console.log('Fetched message ' + id + ' from database.');
182
+ this.write({ success: true, message: 'message_fetched', data: message }, true);
183
+ return {
184
+ content: [{ type: 'text', text: 'Fetched message ' + id }],
185
+ structuredContent: message
186
+ };
187
+ } catch (error) {
188
+ this.log('⛔ Error fetching message from database: ' + error.message + ' (code: ' + error.code + ')');
189
+ }
190
+ return null;
191
+ }
192
+
158
193
  async sendMessageToUser(number = null, message = null, attachments = null) {
159
194
  let jid = this.formatNumber(number) + '@s.whatsapp.net';
160
195
  this.log('begin send message to user ' + jid);
@@ -452,9 +487,14 @@ export default class wahelper {
452
487
  content TEXT,
453
488
  media_data TEXT,
454
489
  media_filename TEXT,
455
- timestamp INTEGER
490
+ timestamp INTEGER,
491
+ \`read\` INTEGER NOT NULL DEFAULT 0
456
492
  );
457
493
  `);
494
+ let cols = this.db.prepare('PRAGMA table_info(messages)').all();
495
+ if (!cols.some(c => c.name === 'read')) {
496
+ this.db.exec('ALTER TABLE messages ADD COLUMN `read` INTEGER NOT NULL DEFAULT 0');
497
+ }
458
498
  } catch (error) {
459
499
  this.log('⛔ Error initing database: ' + error.message + ' (code: ' + error.code + ')');
460
500
  }