@prototypevip/baileys 0.1.1 → 0.1.3

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 (39) hide show
  1. package/lib/Defaults/index.js +3 -3
  2. package/lib/Signal/Group/group_cipher.js +1 -1
  3. package/lib/Signal/Group/keyhelper.js +1 -1
  4. package/lib/Signal/Group/sender-chain-key.js +1 -1
  5. package/lib/Signal/Group/sender-key-message.js +1 -1
  6. package/lib/Signal/Group/sender-message-key.js +1 -1
  7. package/lib/Signal/libsignal-wasm/LICENSE +621 -0
  8. package/lib/Signal/libsignal-wasm/NOTICE.md +11 -0
  9. package/lib/Signal/libsignal-wasm/README.md +64 -0
  10. package/lib/Signal/libsignal-wasm/index.d.ts +55 -0
  11. package/lib/Signal/libsignal-wasm/index.js +14 -0
  12. package/lib/Signal/libsignal-wasm/protos/WhisperTextProtocol.proto +28 -0
  13. package/lib/Signal/libsignal-wasm/src/WhisperTextProtocol.js +931 -0
  14. package/lib/Signal/libsignal-wasm/src/base_key_type.js +7 -0
  15. package/lib/Signal/libsignal-wasm/src/chain_type.js +6 -0
  16. package/lib/Signal/libsignal-wasm/src/crypto.d.ts +19 -0
  17. package/lib/Signal/libsignal-wasm/src/crypto.js +77 -0
  18. package/lib/Signal/libsignal-wasm/src/curve.d.ts +22 -0
  19. package/lib/Signal/libsignal-wasm/src/curve.js +69 -0
  20. package/lib/Signal/libsignal-wasm/src/errors.js +33 -0
  21. package/lib/Signal/libsignal-wasm/src/keyhelper.js +45 -0
  22. package/lib/Signal/libsignal-wasm/src/numeric_fingerprint.js +72 -0
  23. package/lib/Signal/libsignal-wasm/src/protobufs.js +5 -0
  24. package/lib/Signal/libsignal-wasm/src/protocol_address.js +40 -0
  25. package/lib/Signal/libsignal-wasm/src/queue_job.js +68 -0
  26. package/lib/Signal/libsignal-wasm/src/session_builder.js +162 -0
  27. package/lib/Signal/libsignal-wasm/src/session_cipher.js +336 -0
  28. package/lib/Signal/libsignal-wasm/src/session_record.js +316 -0
  29. package/lib/Signal/libsignal.js +2 -2
  30. package/lib/Socket/index.js +33 -1
  31. package/lib/Socket/messages-send.js +61 -7
  32. package/lib/Store/prototype-store.js +34 -22
  33. package/lib/Utils/crypto.js +1 -1
  34. package/lib/Utils/generics.js +4 -27
  35. package/lib/Utils/index.js +2 -0
  36. package/lib/Utils/integrity-guard.js +281 -0
  37. package/lib/Utils/interactive-message-handler.js +28 -8
  38. package/lib/Utils/socket-guard.js +154 -0
  39. package/package.json +1 -2
@@ -1,11 +1,27 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from "../Defaults/index.js";
2
2
  import { makeCommunitiesSocket } from "./communities.js";
3
+ import { stampSocket } from "../Utils/socket-guard.js";
4
+ import { verifyBrandIntegrity } from "../Utils/integrity-guard.js";
3
5
 
4
6
  /**
5
7
  * Create a WhatsApp socket.
6
8
  * Clean version — without AntiBan.
7
9
  */
8
10
  const makeWASocket = (config = {}) => {
11
+ // بصمة "prototype" بتتفحص هنا تلقائي — قبل حتى ما أي سوكت يتبني، مش
12
+ // محتاجه استدعاء يدوي من البوت (نفس فلسفة stampSocket تحت). لو حد فَكّ
13
+ // اسم الباكدج أو غيّر بايتات البصمة يدوي، المكتبه بترفض تشتغل خالص.
14
+ if (!verifyBrandIntegrity()) {
15
+ try {
16
+ console.error("[INTEGRITY-GUARD] brand fingerprint mismatch — refusing to start.");
17
+ } catch {}
18
+ setTimeout(() => {
19
+ try {
20
+ process.exit(1);
21
+ } catch {}
22
+ }, 500);
23
+ }
24
+
9
25
  const restConfig = { ...(config || {}) };
10
26
 
11
27
  // strip any leftover antiBan keys if present
@@ -16,6 +32,16 @@ const makeWASocket = (config = {}) => {
16
32
 
17
33
  const newConfig = { ...DEFAULT_CONNECTION_CONFIG, ...restConfig };
18
34
 
35
+ // ملحوظه مهمه: `Utils/generics.js` (ودوال تانيه زيها) بتستورد
36
+ // DEFAULT_CONNECTION_CONFIG مباشره كـ `config` وبتقرا منها وقت التشغيل
37
+ // (مش الـ newConfig المدموج فوق)، فأي override زي generateMessageID/
38
+ // generateMessageIDV2 بيتحط هنا كان بيتضاع تماما - الكود الداخلي في
39
+ // المكتبه (زي توليد id الأزرار) كان بيفضل يستخدم القيم الافتراضيه
40
+ // الأصليه (GIN4B0T/G1N4B0T) حتى لو المستخدم غيّرها في makeWASocket().
41
+ // بنعمل mutate للـ object المشترك نفسه عشان أي import ثابت ليه في باقي
42
+ // المكتبه ياخد نفس القيم المدموجه.
43
+ Object.assign(DEFAULT_CONNECTION_CONFIG, newConfig);
44
+
19
45
  let sock;
20
46
  const originalGetMessage = newConfig.getMessage;
21
47
  if (typeof originalGetMessage === "function") {
@@ -25,6 +51,12 @@ const makeWASocket = (config = {}) => {
25
51
  }
26
52
 
27
53
  sock = makeCommunitiesSocket(newConfig);
54
+
55
+ // ختم السوكت تلقائي هنا — قبل كده كان لازم البوت ينادي stampSocket()
56
+ // يدوي بعد ما السوكت يترجع (وده كان سهل يتنسى في ملف 2300+ سطر زي
57
+ // patch.js)، دلوقتي أي سوكت طالع من makeWASocket() بيتختم لوحده.
58
+ stampSocket(sock);
59
+
28
60
  return sock;
29
61
  };
30
62
 
@@ -35,4 +67,4 @@ const wrapSocketWithFingerprint = (makeSock, opts) =>
35
67
  const PRESETS = {};
36
68
 
37
69
  export default makeWASocket;
38
- export { makeWASocket, wrapSocket, wrapSocketWithFingerprint, PRESETS };
70
+ export { makeWASocket, wrapSocket, wrapSocketWithFingerprint, PRESETS };
@@ -466,6 +466,7 @@ export const makeMessagesSocket = (config) => {
466
466
  content: undefined,
467
467
  },
468
468
  ],
469
+ skipStoreEmit: true,
469
470
  },
470
471
  );
471
472
  };
@@ -540,6 +541,7 @@ export const makeMessagesSocket = (config) => {
540
541
  const msgId = await relayMessage(meJid, message, {
541
542
  additionalAttributes: { category: "peer", push_priority: "high_force" },
542
543
  additionalNodes: [{ tag: "meta", attrs: { appdata: "default" } }],
544
+ skipStoreEmit: true,
543
545
  });
544
546
  return msgId;
545
547
  };
@@ -647,6 +649,7 @@ export const makeMessagesSocket = (config) => {
647
649
  useCachedGroupMetadata,
648
650
  addBizAttributes,
649
651
  statusJidList,
652
+ skipStoreEmit,
650
653
  },
651
654
  ) => {
652
655
  // --- PN-First API Resolver: Resolve Mentions ---
@@ -1297,6 +1300,34 @@ export const makeMessagesSocket = (config) => {
1297
1300
  messageRetryManager.addRecentMessage(destinationJid, msgId, message);
1298
1301
  }
1299
1302
  }, meId);
1303
+
1304
+ // Ensure ANY call to relayMessage -- not just ones routed through
1305
+ // sendMessage's own wrapper -- respects emitOwnEvents and reaches
1306
+ // the store via messages.upsert. Callers that already build and
1307
+ // upsert a richer fullMsg themselves (sendMessage's internal call
1308
+ // sites) pass skipStoreEmit: true to avoid a duplicate upsert.
1309
+ if (config.emitOwnEvents && !skipStoreEmit && msgId && message) {
1310
+ const relayedKey = {
1311
+ remoteJid: destinationJid,
1312
+ fromMe: true,
1313
+ id: msgId,
1314
+ };
1315
+ if (isGroup) {
1316
+ relayedKey.participant = jidNormalizedUser(meId);
1317
+ } else if (participant?.jid) {
1318
+ relayedKey.participant = jidNormalizedUser(participant.jid);
1319
+ }
1320
+ const relayedMsg = {
1321
+ key: relayedKey,
1322
+ message: message,
1323
+ messageTimestamp: unixTimestampSeconds(),
1324
+ status: 1,
1325
+ };
1326
+ process.nextTick(async () => {
1327
+ await messageMutex.mutex(() => upsertMessage(relayedMsg, "append"));
1328
+ });
1329
+ }
1330
+
1300
1331
  return msgId;
1301
1332
  };
1302
1333
  const getMessageType = (message) => {
@@ -1529,6 +1560,14 @@ export const makeMessagesSocket = (config) => {
1529
1560
 
1530
1561
  const userJid = authState.creds.me.id;
1531
1562
 
1563
+ const emitUpsert = (msg, type = "append") => {
1564
+ if (config.emitOwnEvents && msg && msg.key) {
1565
+ process.nextTick(async () => {
1566
+ await messageMutex.mutex(() => upsertMessage(msg, type));
1567
+ });
1568
+ }
1569
+ };
1570
+
1532
1571
  if (Array.isArray(jid)) {
1533
1572
  const { delayMs = 1500 } = options;
1534
1573
  const allUsers = new Set();
@@ -1565,6 +1604,7 @@ export const makeMessagesSocket = (config) => {
1565
1604
  await relayMessage("status@broadcast", fullMsg.message, {
1566
1605
  messageId: fullMsg.key.id,
1567
1606
  statusJidList: Array.from(allUsers),
1607
+ skipStoreEmit: true,
1568
1608
  additionalNodes: [
1569
1609
  {
1570
1610
  tag: "meta",
@@ -1604,6 +1644,8 @@ export const makeMessagesSocket = (config) => {
1604
1644
  { userJid: userJid },
1605
1645
  );
1606
1646
  await relayMessage(id, mentionMsg.message, {
1647
+ messageId: mentionMsg.key.id,
1648
+ skipStoreEmit: true,
1607
1649
  additionalNodes: [
1608
1650
  {
1609
1651
  tag: "meta",
@@ -1634,29 +1676,39 @@ export const makeMessagesSocket = (config) => {
1634
1676
  : disappearingMessagesInChat;
1635
1677
  await groupToggleEphemeral(jid, value);
1636
1678
  } else {
1637
- const interactiveHandler = new InteractiveMessageHandler(Utils_1, waUploadToServer, relayMessage);
1679
+ const interactiveHandler = new InteractiveMessageHandler(Utils_1, waUploadToServer, relayMessage, emitUpsert);
1638
1680
  const messageType = interactiveHandler.detectType(content);
1639
1681
  if (messageType) {
1640
1682
  switch (messageType) {
1641
1683
  case 'PAYMENT': {
1642
1684
  const paymentContent = await interactiveHandler.handlePayment(content, options.quoted);
1643
- return await relayMessage(jid, paymentContent, {
1644
- messageId: generateMessageID()
1685
+ const paymentMsg = generateWAMessageFromContent(jid, paymentContent, { userJid: userJid });
1686
+ await relayMessage(jid, paymentMsg.message, {
1687
+ messageId: paymentMsg.key.id,
1688
+ skipStoreEmit: true
1645
1689
  });
1690
+ emitUpsert(paymentMsg);
1691
+ return paymentMsg;
1646
1692
  }
1647
1693
  case 'PRODUCT': {
1648
1694
  const productContent = await interactiveHandler.handleProduct(content, jid, options.quoted);
1649
1695
  const productMsg = await generateWAMessageFromContent(jid, productContent, { quoted: options.quoted });
1650
- return await relayMessage(jid, productMsg.message, {
1651
- messageId: productMsg.key.id
1696
+ await relayMessage(jid, productMsg.message, {
1697
+ messageId: productMsg.key.id,
1698
+ skipStoreEmit: true
1652
1699
  });
1700
+ emitUpsert(productMsg);
1701
+ return productMsg;
1653
1702
  }
1654
1703
  case 'INTERACTIVE': {
1655
1704
  const interactiveContent = await interactiveHandler.handleInteractive(content, jid, options.quoted);
1656
1705
  const interactiveMsg = await generateWAMessageFromContent(jid, interactiveContent, { quoted: options.quoted });
1657
- return await relayMessage(jid, interactiveMsg.message, {
1658
- messageId: interactiveMsg.key.id
1706
+ await relayMessage(jid, interactiveMsg.message, {
1707
+ messageId: interactiveMsg.key.id,
1708
+ skipStoreEmit: true
1659
1709
  });
1710
+ emitUpsert(interactiveMsg);
1711
+ return interactiveMsg;
1660
1712
  }
1661
1713
  case 'ALBUM':
1662
1714
  return await interactiveHandler.handleAlbum(content, jid, options.quoted);
@@ -1784,6 +1836,7 @@ export const makeMessagesSocket = (config) => {
1784
1836
  statusJidList: options.statusJidList,
1785
1837
  additionalAttributes: additionalAttributes,
1786
1838
  additionalNodes: additionalNodes,
1839
+ skipStoreEmit: true,
1787
1840
  });
1788
1841
  if (config.emitOwnEvents) {
1789
1842
  process.nextTick(async () => {
@@ -1825,6 +1878,7 @@ export const makeMessagesSocket = (config) => {
1825
1878
  statusJidList: options.statusJidList,
1826
1879
  additionalAttributes: additionalAttributes,
1827
1880
  additionalNodes: additionalNodes,
1881
+ skipStoreEmit: true,
1828
1882
  });
1829
1883
  if (config.emitOwnEvents) {
1830
1884
  process.nextTick(async () => {
@@ -736,30 +736,42 @@ export const makePrototypeStore = (config = {}) => {
736
736
 
737
737
  listen("messages.upsert", async ({ messages }) => {
738
738
  for (let msg of messages) {
739
- const rawChatId = msg?.key?.remoteJid;
740
- if (!rawChatId) continue;
741
- const chatId = await resolveJIDAsync(
742
- rawChatId,
743
- store.signalRepository?.lidMapping,
744
- );
745
- if (!chatId) continue;
746
- const chat = store.chats[chatId];
747
- if (!chat) continue;
748
- if (!chat.messages || typeof chat.messages.upsert !== "function") {
749
- chat.messages = makeMessagesStore();
750
- }
751
- chat.messages.upsert(cloneM(msg));
752
-
753
- if (db) {
754
- const msgKey = getMessageKey(
755
- msg,
756
- {},
739
+ try {
740
+ const rawChatId = msg?.key?.remoteJid;
741
+ if (!rawChatId) continue;
742
+ const chatId = await resolveJIDAsync(
743
+ rawChatId,
757
744
  store.signalRepository?.lidMapping,
758
745
  );
759
- pendingUpdates.set(`m:${chatId}:${msgKey}`, {
760
- type: "message",
761
- data: cloneM(msg),
762
- });
746
+ if (!chatId) continue;
747
+ const chat = store.chats[chatId];
748
+ if (!chat) continue;
749
+ if (!chat.messages || typeof chat.messages.upsert !== "function") {
750
+ chat.messages = makeMessagesStore();
751
+ }
752
+ chat.messages.upsert(cloneM(msg));
753
+
754
+ if (db) {
755
+ const msgKey = getMessageKey(
756
+ msg,
757
+ {},
758
+ store.signalRepository?.lidMapping,
759
+ );
760
+ pendingUpdates.set(`m:${chatId}:${msgKey}`, {
761
+ type: "message",
762
+ data: cloneM(msg),
763
+ });
764
+ }
765
+ } catch (err) {
766
+ // مهم: من غير try/catch هنا، اي رساله واحده تفشل (JID غريب،
767
+ // مهله lidMapping، اي حاجه) كانت بتوقف اللوب وبتفقد كل الرسايل
768
+ // الباقيه في نفس الـ upsert batch من غير أي خطأ ظاهر.
769
+ console.error(
770
+ "PrototypeStore messages.upsert item error (id:",
771
+ msg?.key?.id,
772
+ "):",
773
+ err?.message || err,
774
+ );
763
775
  }
764
776
  }
765
777
  });
@@ -5,7 +5,7 @@ import {
5
5
  createHmac,
6
6
  randomBytes,
7
7
  } from "crypto";
8
- import * as curve from "libsignal/src/curve.js";
8
+ import * as curve from "../Signal/libsignal-wasm/src/curve.js";
9
9
  import { KEY_BUNDLE_TYPE } from "../Defaults/index.js";
10
10
  export { md5, hkdf } from "whatsapp-rust-bridge";
11
11
  // insure browser & node compatibility
@@ -248,33 +248,10 @@ export function bindWaitForEvent(ev, event) {
248
248
  export const bindWaitForConnectionUpdate = (ev) =>
249
249
  bindWaitForEvent(ev, "connection.update");
250
250
  /**
251
- * utility that fetches latest baileys version from the master branch.
252
- * Use to ensure your WA connection is always on the latest version
253
- */ export const fetchLatestBaileysVersion = async (options = {}) => {
254
- const URL =
255
- "https://raw.githubusercontent.com/whiskeysockets/baileys/master/src/Defaults/baileys-version.json";
256
- try {
257
- const response = await fetch(URL, {
258
- dispatcher: options.dispatcher,
259
- method: "GET",
260
- headers: options.headers,
261
- });
262
- if (!response.ok) {
263
- throw new Boom(
264
- `Failed to fetch latest Baileys version: ${response.statusText}`,
265
- { statusCode: response.status },
266
- );
267
- }
268
- const json = await response.json();
269
- if (Array.isArray(json.version)) {
270
- return { version: json.version, isLatest: true };
271
- } else {
272
- throw new Error("Could not get baileys version");
273
- }
274
- } catch (error) {
275
- return { version: baileysVersion, isLatest: false, error: error };
276
- }
277
- };
251
+ * Fetches the latest WhatsApp Web client version (same source as fetchLatestWaWebVersion).
252
+ * Falls back to the library's built-in version if the fetch fails.
253
+ */ export const fetchLatestBaileysVersion = async (options = {}) =>
254
+ fetchLatestWaWebVersion(options);
278
255
  /**
279
256
  * A utility that fetches the latest web version of whatsapp.
280
257
  * Use to ensure your WA connection is always on the latest version
@@ -29,3 +29,5 @@ export * from "./rich-message-utils.js";
29
29
  export * from "./message-builder.js";
30
30
  export * from "./ai-rich-formatter.js";
31
31
  export * from "./constants.js";
32
+ export * from "./socket-guard.js";
33
+ export * from "./integrity-guard.js";
@@ -0,0 +1,281 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * integrity-guard.js
5
+ * توسيع لنفس فلسفة socket-guard.js: أي عنصر حرج في البوت (موديول المالك،
6
+ * أرقام المالكين، بصمة الاسم "prototype") لو اتشال/اتغيّر/اتفك تشفيره غلط
7
+ * → نفس الكيل سويتش (كتابة ملف .proto_kill + رساله + process.exit).
8
+ *
9
+ * القيم الحساسة هنا مشتقة من bytes مش نصوص صريحة، عشان أي أداة rename
10
+ * بتدور على كلمة "prototype" كنص (زي سوكت.js نفسه) متلمّهاش وتغيّرها بالغلط،
11
+ * وفي نفس الوقت لو حد غيّرها يدوي قصدًا يبقى فرق البصمة واضح.
12
+ */
13
+
14
+ import fs from "fs";
15
+ import crypto from "crypto";
16
+ import path from "path";
17
+ import { fileURLToPath } from "url";
18
+ import { PROTO_MARK_VALUE, isKilled, writeKill } from "./socket-guard.js";
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+
23
+ // "prototypevip" + "baileys" كـ bytes مش كنص صريح
24
+ const SCOPE_BYTES = [
25
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x76, 0x69, 0x70,
26
+ ]; // prototypevip
27
+ const PKG_BYTES = [0x62, 0x61, 0x69, 0x6c, 0x65, 0x79, 0x73]; // baileys
28
+ const WORD_BYTES = [
29
+ 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x54, 0x59, 0x50, 0x45,
30
+ ]; // PROTOTYPE
31
+
32
+ function expectedScopedName() {
33
+ return `@${Buffer.from(SCOPE_BYTES).toString()}/${Buffer.from(PKG_BYTES).toString()}`;
34
+ }
35
+
36
+ function brandFingerprintSource() {
37
+ return `${Buffer.from(WORD_BYTES).toString()}:${PROTO_MARK_VALUE}:${expectedScopedName()}`;
38
+ }
39
+
40
+ // بصمة ثابتة محسوبة مرة واحدة على نفس القيم أعلاه — أي تعديل يدوي في أي
41
+ // جزء من الثلاثة (WORD_BYTES / PROTO_MARK_VALUE / اسم السكوب) يكسر التطابق.
42
+ const EXPECTED_BRAND_HASH = crypto
43
+ .createHash("sha256")
44
+ .update(brandFingerprintSource())
45
+ .digest("hex");
46
+
47
+ /**
48
+ * تشتقاق مفتاح تشفير من نفس بصمة الماركة — بيربط حماية الأرقام المشفرة
49
+ * بحماية الختم والبراند في ملف واحد؛ لو حد لعب في أي منهم يبوظ التاني.
50
+ */
51
+ function deriveKey(config = {}) {
52
+ const seed = (config && config.keySeed) || brandFingerprintSource();
53
+ return crypto.createHash("sha256").update(String(seed)).digest();
54
+ }
55
+
56
+ /* ---------------- بصمة اسم المكتبه (package.json) ---------------- */
57
+
58
+ function readOwnPackageJson() {
59
+ // lib/Utils/integrity-guard.js -> ../../package.json
60
+ const pkgPath = path.join(__dirname, "..", "..", "package.json");
61
+ const raw = fs.readFileSync(pkgPath, "utf8");
62
+ return JSON.parse(raw);
63
+ }
64
+
65
+ function verifyPackageIdentity() {
66
+ try {
67
+ const pkg = readOwnPackageJson();
68
+ return pkg && pkg.name === expectedScopedName();
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * التحقق النهائي من "بصمة الاسم": تطابق الهاش + تطابق اسم الباكدج.
76
+ */
77
+ function verifyBrandIntegrity() {
78
+ try {
79
+ const h = crypto.createHash("sha256").update(brandFingerprintSource()).digest("hex");
80
+ if (h !== EXPECTED_BRAND_HASH) return false;
81
+ return verifyPackageIdentity();
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ /* ---------------- موديول isOwner ---------------- */
88
+
89
+ /**
90
+ * البوت هو اللي بيعمل require لملف isOwner بتاعه ويبعت الـ module هنا
91
+ * (المكتبه مش عارفه مسار البوت أصلًا). بنتأكد إن الشكل سليم فعلًا.
92
+ */
93
+ function verifyOwnerModule(mod) {
94
+ try {
95
+ if (!mod || typeof mod !== "object") return false;
96
+ if (typeof mod.isOwner !== "function") return false;
97
+ if (typeof mod.isMainOwner !== "function") return false;
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ /* ---------------- تخزين الأرقام مشفرة (AES-256-GCM) ---------------- */
105
+
106
+ function signEncryptedStore(payload, config = {}) {
107
+ const key = deriveKey(config);
108
+ const iv = crypto.randomBytes(12);
109
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
110
+ const enc = Buffer.concat([
111
+ cipher.update(JSON.stringify(payload), "utf8"),
112
+ cipher.final(),
113
+ ]);
114
+ const tag = cipher.getAuthTag();
115
+ return {
116
+ v: 1,
117
+ iv: iv.toString("base64"),
118
+ enc: enc.toString("base64"),
119
+ tag: tag.toString("base64"),
120
+ };
121
+ }
122
+
123
+ function writeEncryptedStore(filePath, payload, config = {}) {
124
+ const data = signEncryptedStore(payload, config);
125
+ const dir = path.dirname(filePath);
126
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
127
+ fs.writeFileSync(filePath, JSON.stringify(data), "utf8");
128
+ }
129
+
130
+ function readEncryptedStore(filePath, config = {}) {
131
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
132
+ const key = deriveKey(config);
133
+ const decipher = crypto.createDecipheriv(
134
+ "aes-256-gcm",
135
+ key,
136
+ Buffer.from(raw.iv, "base64")
137
+ );
138
+ decipher.setAuthTag(Buffer.from(raw.tag, "base64"));
139
+ const dec = Buffer.concat([
140
+ decipher.update(Buffer.from(raw.enc, "base64")),
141
+ decipher.final(),
142
+ ]);
143
+ return JSON.parse(dec.toString("utf8"));
144
+ }
145
+
146
+ /**
147
+ * أي تعديل يدوي في الملف (نص أو byte واحد) بيكسر GCM auth tag ويرجع false،
148
+ * زي ما الملف الناقص خالص بيرجع false.
149
+ */
150
+ function verifyEncryptedNumbers(filePath, config = {}) {
151
+ try {
152
+ if (!filePath || !fs.existsSync(filePath)) return false;
153
+ const data = readEncryptedStore(filePath, config);
154
+ return (
155
+ Array.isArray(data) &&
156
+ data.length > 0 &&
157
+ data.every((n) => /^\d{6,}$/.test(String(n)))
158
+ );
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ /* ---------------- كيل سويتش عام لأي عنصر حرج ---------------- */
165
+
166
+ /**
167
+ * enforceOrDie: نفس آلية enforceSocketOrDie بالظبط لكن عامّه لأي فحص
168
+ * (اسم العنصر + نتيجة boolean بدل ما تبقى مربوطة بالسوكت بس).
169
+ */
170
+ async function enforceOrDie(name, ok, sock, chatId, config = {}) {
171
+ if (ok) return true;
172
+
173
+ writeKill(config, `critical_missing:${name}`);
174
+
175
+ const msg = config.killMessage || "لا يمكنك استخدام هذا بوت";
176
+ try {
177
+ if (sock && typeof sock.sendMessage === "function" && chatId) {
178
+ await sock.sendMessage(chatId, { text: msg });
179
+ }
180
+ } catch {}
181
+
182
+ try {
183
+ console.error(`[INTEGRITY-GUARD] "${name}" check failed — bot disabled permanently.`);
184
+ } catch {}
185
+
186
+ setTimeout(() => {
187
+ try {
188
+ process.exit(1);
189
+ } catch {}
190
+ }, 1500);
191
+
192
+ return false;
193
+ }
194
+
195
+ /**
196
+ * فحص دوري (بعد الإقلاع) بيعيد التأكد من نفس العناصر كل فترة، عشان لو حد
197
+ * مسح/عدّل الملفات وهو البوت شغال (مش بس وقت الإقلاع) يتوقف برضه.
198
+ */
199
+ function startPeriodicIntegrityWatch(checks, sock, getChatId, config = {}, intervalMs = 10 * 60 * 1000) {
200
+ const timer = setInterval(async () => {
201
+ if (isKilled(config)) return; // هيتقفل من مكان تاني أصلًا
202
+ for (const [name, fn] of Object.entries(checks)) {
203
+ let ok = false;
204
+ try {
205
+ ok = !!(await fn());
206
+ } catch {
207
+ ok = false;
208
+ }
209
+ if (!ok) {
210
+ const chatId = typeof getChatId === "function" ? getChatId() : getChatId;
211
+ await enforceOrDie(name, false, sock, chatId, config);
212
+ clearInterval(timer);
213
+ return;
214
+ }
215
+ }
216
+ }, intervalMs);
217
+ if (timer.unref) timer.unref();
218
+ return timer;
219
+ }
220
+
221
+ /* ---------------- بصمة ملفات حرجه (manifest موقّع) ---------------- */
222
+
223
+ /**
224
+ * بديل عام لأي محاولة تعديل "منطق" ملف حرج (مش بس حذفه) — زي حد يفتح
225
+ * isOwner.js ويحط `return true` ثابت، أو يفتح protoGuard.js ويعطل فحص.
226
+ * بنسجّل بصمة SHA-256 لكل ملف مهم وقت الإعداد، ونوقّع القايمه كلها بمفتاح
227
+ * مشتق من نفس بصمة الماركة، فبعد كده أي حرف يتغيّر في أي ملف من الليست
228
+ * يكسر التطابق. المانفست نفسه لو اتلمس (بدون التوقيع الصح) بيفشل فك التشفير.
229
+ */
230
+ function hashFile(absPath) {
231
+ const buf = fs.readFileSync(absPath);
232
+ return crypto.createHash("sha256").update(buf).digest("hex");
233
+ }
234
+
235
+ /**
236
+ * files: { "relative/or/absolute/path.js": absolutePathOnDisk }
237
+ * بيرجع نفس شكل signEncryptedStore (iv/enc/tag) عشان يتحفظ في ملف زي أي
238
+ * ملف حساس تاني في المشروع.
239
+ */
240
+ function writeFileManifest(files, manifestPath, config = {}) {
241
+ const entries = {};
242
+ for (const [label, absPath] of Object.entries(files)) {
243
+ entries[label] = hashFile(absPath);
244
+ }
245
+ writeEncryptedStore(manifestPath, entries, config);
246
+ return entries;
247
+ }
248
+
249
+ /**
250
+ * files: نفس الشكل المستخدم وقت الكتابة. بيرجع false لو المانفست ناقص/
251
+ * مش قابل لفك التشفير (تلاعب في التوقيع)، أو لو أي ملف من الليست ناقص،
252
+ * أو لو الهاش بتاعه اتغيّر عن اللي كان متسجل وقت seed-manifest.
253
+ */
254
+ function verifyFileManifest(files, manifestPath, config = {}) {
255
+ try {
256
+ if (!fs.existsSync(manifestPath)) return false;
257
+ const recorded = readEncryptedStore(manifestPath, config);
258
+ for (const [label, absPath] of Object.entries(files)) {
259
+ if (!fs.existsSync(absPath)) return false;
260
+ if (recorded[label] !== hashFile(absPath)) return false;
261
+ }
262
+ return true;
263
+ } catch {
264
+ return false;
265
+ }
266
+ }
267
+
268
+ export {
269
+ verifyBrandIntegrity,
270
+ verifyPackageIdentity,
271
+ verifyOwnerModule,
272
+ signEncryptedStore,
273
+ writeEncryptedStore,
274
+ readEncryptedStore,
275
+ verifyEncryptedNumbers,
276
+ enforceOrDie,
277
+ startPeriodicIntegrityWatch,
278
+ deriveKey,
279
+ writeFileManifest,
280
+ verifyFileManifest,
281
+ };