@badzz88/baileys 8.5.4 → 8.5.6

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 (269) hide show
  1. package/README.md +2 -2
  2. package/WAProto/fix-imports.js +69 -79
  3. package/WAProto/index.d.ts +15305 -87935
  4. package/WAProto/index.js +41109 -194076
  5. package/engine-requirements.js +15 -8
  6. package/package.json +23 -19
  7. package/src/Defaults/index.js +186 -0
  8. package/src/Signal/Group/ciphertext-message.js +15 -0
  9. package/src/Signal/Group/group-session-builder.js +86 -0
  10. package/src/Signal/Group/group_cipher.js +82 -0
  11. package/src/Signal/Group/index.js +140 -0
  12. package/src/Signal/Group/keyhelper.js +77 -0
  13. package/src/Signal/Group/sender-chain-key.js +29 -0
  14. package/src/Signal/Group/sender-key-distribution-message.js +66 -0
  15. package/src/Signal/Group/sender-key-message.js +69 -0
  16. package/src/Signal/Group/sender-key-name.js +51 -0
  17. package/src/Signal/Group/sender-key-record.js +44 -0
  18. package/src/Signal/Group/sender-key-state.js +87 -0
  19. package/src/Signal/Group/sender-message-key.js +29 -0
  20. package/src/Signal/libsignal.js +455 -0
  21. package/src/Signal/lid-mapping.js +273 -0
  22. package/src/Socket/Client/index.js +31 -0
  23. package/src/Socket/Client/types.js +13 -0
  24. package/src/Socket/Client/websocket.js +61 -0
  25. package/src/Socket/aigroups.js +221 -0
  26. package/src/Socket/business.js +411 -0
  27. package/src/Socket/chats.js +1449 -0
  28. package/src/Socket/communities.js +463 -0
  29. package/src/Socket/graphql.js +523 -0
  30. package/src/Socket/groups.js +516 -0
  31. package/src/Socket/index.js +31 -0
  32. package/src/Socket/interactive-handler.js +527 -0
  33. package/src/Socket/interop.js +339 -0
  34. package/src/Socket/luxu.js +349 -0
  35. package/src/Socket/managed-account.js +98 -0
  36. package/src/Socket/messages-recv.js +2685 -0
  37. package/src/Socket/messages-send.js +2047 -0
  38. package/src/Socket/mex.js +57 -0
  39. package/src/Socket/newsletter.js +455 -0
  40. package/src/Socket/privacy.js +125 -0
  41. package/src/Socket/registration.js +236 -0
  42. package/src/Socket/socket.js +983 -0
  43. package/src/Socket/text-router.js +65 -0
  44. package/src/Socket/username.js +130 -0
  45. package/src/Store/index.js +33 -0
  46. package/src/Store/keyed-db.js +118 -0
  47. package/src/Store/make-cache-manager-store.js +84 -0
  48. package/src/Store/make-in-memory-store.js +551 -0
  49. package/src/Store/make-ordered-dictionary.js +81 -0
  50. package/src/Store/object-repository.js +26 -0
  51. package/src/Types/Auth.js +31 -0
  52. package/src/Types/Bussines.js +2 -0
  53. package/src/Types/Call.js +2 -0
  54. package/src/Types/Chat.js +4 -0
  55. package/src/Types/Contact.js +2 -0
  56. package/src/Types/Events.js +2 -0
  57. package/src/Types/GroupMetadata.js +2 -0
  58. package/src/Types/Label.js +26 -0
  59. package/src/Types/LabelAssociation.js +8 -0
  60. package/src/Types/Message.js +93 -0
  61. package/src/Types/Mex.js +112 -0
  62. package/src/Types/Newsletter.js +109 -0
  63. package/src/Types/Product.js +2 -0
  64. package/src/Types/Signal.js +2 -0
  65. package/src/Types/Socket.js +2 -0
  66. package/src/Types/State.js +62 -0
  67. package/src/Types/USync.js +2 -0
  68. package/src/Types/index.js +56 -0
  69. package/src/Utils/auth-utils.js +254 -0
  70. package/src/Utils/browser-utils.js +112 -0
  71. package/src/Utils/business.js +240 -0
  72. package/src/Utils/chat-utils.js +1230 -0
  73. package/src/Utils/command-loader.d.ts +27 -0
  74. package/src/Utils/command-loader.js +89 -0
  75. package/src/Utils/companion-reg-client-utils.js +40 -0
  76. package/src/Utils/consumer-application.js +105 -0
  77. package/src/Utils/crypto.js +118 -0
  78. package/src/Utils/curve25519-js.js +242 -0
  79. package/src/Utils/decode-wa-message.js +529 -0
  80. package/src/Utils/event-buffer.js +580 -0
  81. package/src/Utils/generics.js +483 -0
  82. package/src/Utils/group-history.js +44 -0
  83. package/src/Utils/history.js +232 -0
  84. package/src/Utils/identity-change-handler.js +52 -0
  85. package/src/Utils/index.js +58 -0
  86. package/src/Utils/jid-display-normalization.js +195 -0
  87. package/src/Utils/link-preview.js +135 -0
  88. package/src/Utils/logger.js +8 -0
  89. package/src/Utils/lt-hash.js +25 -0
  90. package/src/Utils/make-mutex.js +36 -0
  91. package/src/Utils/message-composer.js +471 -0
  92. package/src/Utils/message-retry-manager.js +217 -0
  93. package/src/Utils/messages-media.js +843 -0
  94. package/src/Utils/messages.js +2817 -0
  95. package/src/Utils/meta-ai-msmsg.js +222 -0
  96. package/src/Utils/native-bridge.js +68 -0
  97. package/src/Utils/noise-handler.js +169 -0
  98. package/src/Utils/offline-node-processor.js +34 -0
  99. package/src/Utils/pre-key-manager.js +90 -0
  100. package/src/Utils/process-message.js +950 -0
  101. package/src/Utils/reporting-utils.js +261 -0
  102. package/src/Utils/session-pool.d.ts +14 -0
  103. package/src/Utils/session-pool.js +70 -0
  104. package/src/Utils/signal.js +217 -0
  105. package/src/Utils/stanza-ack.js +30 -0
  106. package/src/Utils/sticker.d.ts +13 -0
  107. package/src/Utils/sticker.js +123 -0
  108. package/src/Utils/sync-action-utils.js +45 -0
  109. package/src/Utils/tc-token-utils.js +158 -0
  110. package/src/Utils/use-multi-file-auth-state.js +111 -0
  111. package/src/Utils/validate-connection.js +209 -0
  112. package/src/Utils/view-once-cache.d.ts +12 -0
  113. package/src/Utils/view-once-cache.js +63 -0
  114. package/src/Utils/voip-rekey.js +22 -0
  115. package/src/WABinary/constants.js +1304 -0
  116. package/src/WABinary/decode.js +342 -0
  117. package/src/WABinary/encode.js +225 -0
  118. package/src/WABinary/generic-utils.js +238 -0
  119. package/src/WABinary/index.js +34 -0
  120. package/src/WABinary/jid-utils.js +351 -0
  121. package/src/WABinary/types.js +2 -0
  122. package/src/WAM/BinaryInfo.js +13 -0
  123. package/src/WAM/constants.js +39484 -0
  124. package/src/WAM/encode.js +149 -0
  125. package/src/WAM/index.js +32 -0
  126. package/src/WAUSync/Protocols/USyncBotProfileProtocol.js +53 -0
  127. package/src/WAUSync/Protocols/USyncBusinessProtocol.js +44 -0
  128. package/src/WAUSync/Protocols/USyncContactProtocol.js +55 -0
  129. package/src/WAUSync/Protocols/USyncDeviceProtocol.js +55 -0
  130. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.js +31 -0
  131. package/src/WAUSync/Protocols/USyncFeatureProtocol.js +54 -0
  132. package/src/WAUSync/Protocols/USyncLIDProtocol.js +32 -0
  133. package/src/WAUSync/Protocols/USyncNewsletterProtocol.js +473 -0
  134. package/src/WAUSync/Protocols/USyncPictureProtocol.js +34 -0
  135. package/src/WAUSync/Protocols/USyncSidelistProtocol.js +29 -0
  136. package/src/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
  137. package/src/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
  138. package/src/WAUSync/Protocols/USyncUsernameProtocol.js +28 -0
  139. package/src/WAUSync/Protocols/index.js +40 -0
  140. package/src/WAUSync/USyncQuery.js +126 -0
  141. package/src/WAUSync/USyncUser.js +50 -0
  142. package/src/WAUSync/index.js +32 -0
  143. package/src/antiban.js +4152 -0
  144. package/{lib → src}/index.js +51 -49
  145. package/lib/Defaults/index.js +0 -195
  146. package/lib/Signal/Group/ciphertext-message.js +0 -15
  147. package/lib/Signal/Group/group-session-builder.js +0 -92
  148. package/lib/Signal/Group/group_cipher.js +0 -89
  149. package/lib/Signal/Group/index.js +0 -136
  150. package/lib/Signal/Group/keyhelper.js +0 -73
  151. package/lib/Signal/Group/sender-chain-key.js +0 -32
  152. package/lib/Signal/Group/sender-key-distribution-message.js +0 -66
  153. package/lib/Signal/Group/sender-key-message.js +0 -69
  154. package/lib/Signal/Group/sender-key-name.js +0 -50
  155. package/lib/Signal/Group/sender-key-record.js +0 -44
  156. package/lib/Signal/Group/sender-key-state.js +0 -97
  157. package/lib/Signal/Group/sender-message-key.js +0 -30
  158. package/lib/Signal/libsignal.js +0 -470
  159. package/lib/Signal/lid-mapping.js +0 -280
  160. package/lib/Socket/Client/index.js +0 -30
  161. package/lib/Socket/Client/types.js +0 -13
  162. package/lib/Socket/Client/websocket.js +0 -62
  163. package/lib/Socket/aigroups.js +0 -240
  164. package/lib/Socket/business.js +0 -414
  165. package/lib/Socket/chats.js +0 -2165
  166. package/lib/Socket/communities.js +0 -545
  167. package/lib/Socket/graphql.js +0 -863
  168. package/lib/Socket/groups.js +0 -685
  169. package/lib/Socket/index.js +0 -41
  170. package/lib/Socket/interactive-handler.js +0 -579
  171. package/lib/Socket/interop.js +0 -430
  172. package/lib/Socket/managed-account.js +0 -214
  173. package/lib/Socket/messages-recv.js +0 -3000
  174. package/lib/Socket/messages-send.js +0 -2155
  175. package/lib/Socket/mex.js +0 -47
  176. package/lib/Socket/newsletter.js +0 -814
  177. package/lib/Socket/privacy.js +0 -261
  178. package/lib/Socket/registration.js +0 -434
  179. package/lib/Socket/socket.js +0 -1074
  180. package/lib/Socket/username.js +0 -160
  181. package/lib/Store/index.js +0 -36
  182. package/lib/Store/make-cache-manager-store.js +0 -90
  183. package/lib/Store/make-in-memory-store.js +0 -488
  184. package/lib/Store/make-ordered-dictionary.js +0 -81
  185. package/lib/Store/object-repository.js +0 -29
  186. package/lib/Types/Auth.js +0 -38
  187. package/lib/Types/Bussines.js +0 -2
  188. package/lib/Types/Call.js +0 -2
  189. package/lib/Types/Chat.js +0 -4
  190. package/lib/Types/Contact.js +0 -2
  191. package/lib/Types/Events.js +0 -2
  192. package/lib/Types/GroupMetadata.js +0 -2
  193. package/lib/Types/Label.js +0 -27
  194. package/lib/Types/LabelAssociation.js +0 -9
  195. package/lib/Types/Message.js +0 -95
  196. package/lib/Types/Newsletter.js +0 -152
  197. package/lib/Types/Product.js +0 -2
  198. package/lib/Types/Signal.js +0 -2
  199. package/lib/Types/Socket.js +0 -2
  200. package/lib/Types/State.js +0 -70
  201. package/lib/Types/USync.js +0 -2
  202. package/lib/Types/index.js +0 -55
  203. package/lib/Utils/auth-utils.js +0 -301
  204. package/lib/Utils/browser-utils.js +0 -114
  205. package/lib/Utils/business.js +0 -243
  206. package/lib/Utils/chat-utils.js +0 -1272
  207. package/lib/Utils/consumer-application.js +0 -107
  208. package/lib/Utils/crypto.js +0 -125
  209. package/lib/Utils/decode-wa-message.js +0 -793
  210. package/lib/Utils/event-buffer.js +0 -583
  211. package/lib/Utils/generics.js +0 -617
  212. package/lib/Utils/group-history.js +0 -51
  213. package/lib/Utils/history.js +0 -244
  214. package/lib/Utils/identity-change-handler.js +0 -52
  215. package/lib/Utils/index.js +0 -53
  216. package/lib/Utils/jid-display-normalization.js +0 -218
  217. package/lib/Utils/link-preview.js +0 -138
  218. package/lib/Utils/logger.js +0 -9
  219. package/lib/Utils/lt-hash.js +0 -6
  220. package/lib/Utils/make-mutex.js +0 -36
  221. package/lib/Utils/message-composer.js +0 -479
  222. package/lib/Utils/message-inspect.js +0 -393
  223. package/lib/Utils/message-retry-manager.js +0 -205
  224. package/lib/Utils/messages-media.js +0 -925
  225. package/lib/Utils/messages.js +0 -2482
  226. package/lib/Utils/meta-ai-msmsg.js +0 -122
  227. package/lib/Utils/noise-handler.js +0 -194
  228. package/lib/Utils/offline-node-processor.js +0 -37
  229. package/lib/Utils/pre-key-manager.js +0 -97
  230. package/lib/Utils/process-message.js +0 -1139
  231. package/lib/Utils/reporting-utils.js +0 -262
  232. package/lib/Utils/signal.js +0 -189
  233. package/lib/Utils/stanza-ack.js +0 -67
  234. package/lib/Utils/sync-action-utils.js +0 -51
  235. package/lib/Utils/tc-token-utils.js +0 -156
  236. package/lib/Utils/use-multi-file-auth-state.js +0 -137
  237. package/lib/Utils/validate-connection.js +0 -259
  238. package/lib/Utils/voip-rekey.js +0 -22
  239. package/lib/WABinary/constants.js +0 -1304
  240. package/lib/WABinary/decode.js +0 -377
  241. package/lib/WABinary/encode.js +0 -58
  242. package/lib/WABinary/generic-utils.js +0 -150
  243. package/lib/WABinary/index.js +0 -33
  244. package/lib/WABinary/jid-utils.js +0 -374
  245. package/lib/WABinary/types.js +0 -2
  246. package/lib/WAM/BinaryInfo.js +0 -13
  247. package/lib/WAM/constants.js +0 -39486
  248. package/lib/WAM/encode.js +0 -142
  249. package/lib/WAM/index.js +0 -31
  250. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -55
  251. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +0 -100
  252. package/lib/WAUSync/Protocols/USyncContactProtocol.js +0 -60
  253. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +0 -65
  254. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +0 -27
  255. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +0 -72
  256. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +0 -31
  257. package/lib/WAUSync/Protocols/USyncNewsletterProtocol.js +0 -865
  258. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +0 -32
  259. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +0 -29
  260. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +0 -44
  261. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -38
  262. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +0 -28
  263. package/lib/WAUSync/Protocols/index.js +0 -40
  264. package/lib/WAUSync/USyncBackoff.js +0 -26
  265. package/lib/WAUSync/USyncQuery.js +0 -204
  266. package/lib/WAUSync/USyncUser.js +0 -58
  267. package/lib/WAUSync/index.js +0 -32
  268. package/lib/antiban.js +0 -4391
  269. /package/{lib → src}/Defaults/phonenumber-mcc.json +0 -0
package/lib/antiban.js DELETED
@@ -1,4391 +0,0 @@
1
- const __importMetaUrl = require('url').pathToFileURL(__filename).href
2
- ;('use strict')
3
- var __create = Object.create
4
- var __defProp = Object.defineProperty
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor
6
- var __getOwnPropNames = Object.getOwnPropertyNames
7
- var __getProtoOf = Object.getPrototypeOf
8
- var __hasOwnProp = Object.prototype.hasOwnProperty
9
- var __export = (target, all) => {
10
- for (var name in all) __defProp(target, name, { get: all[name], enumerable: true })
11
- }
12
- var __copyProps = (to, from, except, desc) => {
13
- if ((from && typeof from === 'object') || typeof from === 'function') {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, {
17
- get: () => from[key],
18
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
- })
20
- }
21
- return to
22
- }
23
- var __toESM = (mod, isNodeMode, target) => (
24
- (target = mod != null ? __create(__getProtoOf(mod)) : {}),
25
- __copyProps(
26
-
27
-
28
-
29
-
30
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, 'default', { value: mod, enumerable: true }) : target,
31
- mod
32
- )
33
- )
34
- var __toCommonJS = mod => __copyProps(__defProp({}, '__esModule', { value: true }), mod)
35
-
36
-
37
- var index_exports = {}
38
- __export(index_exports, {
39
- AntiBan: () => AntiBan,
40
- ContactGraphWarmer: () => ContactGraphWarmer,
41
- ContentVariator: () => ContentVariator,
42
- FileStateAdapter: () => FileStateAdapter,
43
- HealthMonitor: () => HealthMonitor,
44
- JidCanonicalizer: () => JidCanonicalizer,
45
- LidFirstResolver: () => LidFirstResolver,
46
- LidResolver: () => LidResolver,
47
- MAC_ERROR_CODES: () => MAC_ERROR_CODES,
48
- MessageQueue: () => MessageQueue,
49
- MessageRetryReason: () => MessageRetryReason,
50
- PRESETS: () => PRESETS,
51
- PostReconnectThrottle: () => PostReconnectThrottle,
52
- PresenceChoreographer: () => PresenceChoreographer,
53
- RateLimiter: () => RateLimiter,
54
- ReplyRatioGuard: () => ReplyRatioGuard,
55
- RetryReasonTracker: () => RetryReasonTracker,
56
- Scheduler: () => Scheduler,
57
- SessionHealthMonitor: () => SessionHealthMonitor,
58
- StateManager: () => StateManager,
59
- TimelockGuard: () => TimelockGuard,
60
- WarmUp: () => WarmUp,
61
- WebhookAlerts: () => WebhookAlerts,
62
- applyFingerprint: () => applyFingerprint,
63
- applyGroupMultiplier: () => applyGroupMultiplier,
64
- buildContentSignature: () => buildContentSignature,
65
- classifyDisconnect: () => classifyDisconnect,
66
- createLidFirstResolver: () => createLidFirstResolver,
67
- credsSnapshot: () => credsSnapshot,
68
- generateFingerprint: () => generateFingerprint,
69
- getCircadianMultiplier: () => getCircadianMultiplier,
70
- getRetryReasonDescription: () => getRetryReasonDescription,
71
- isBroadcast: () => isBroadcast,
72
- isGroup: () => isGroup,
73
- isMacError: () => isMacError,
74
- isNewsletter: () => isNewsletter,
75
- messageRecovery: () => messageRecovery,
76
- parseRetryReason: () => parseRetryReason,
77
- proxyRotator: () => proxyRotator,
78
- readReceiptVariance: () => readReceiptVariance,
79
- resolveConfig: () => resolveConfig,
80
- shouldUseGroupProfile: () => shouldUseGroupProfile,
81
- wrapSocket: () => wrapSocket,
82
- wrapWithSessionStability: () => wrapWithSessionStability
83
- })
84
- module.exports = __toCommonJS(index_exports)
85
-
86
-
87
- var TIME_CONSTANTS = {
88
- MS_PER_SECOND: 1e3,
89
- MS_PER_MINUTE: 6e4,
90
- MS_PER_HOUR: 36e5,
91
- MS_PER_DAY: 864e5,
92
- BURST_RESET_MS: 3e4,
93
- IDENTICAL_WINDOW_MS: 18e5
94
-
95
- }
96
- var DEFAULT_CONFIG = {
97
- maxPerMinute: 12,
98
- maxPerHour: 400,
99
- maxPerDay: 2500,
100
- minDelayMs: 1200,
101
- maxDelayMs: 4e3,
102
- newChatDelayMs: 2e3,
103
- maxIdenticalMessages: 10,
104
- burstAllowance: 5,
105
- identicalMessageWindowMs: TIME_CONSTANTS.IDENTICAL_WINDOW_MS
106
- }
107
- var RateLimiter = class {
108
- config
109
- messages = []
110
- identicalCount = new Map()
111
- knownChats = new Set()
112
- burstCount = 0
113
- lastMessageTime = 0
114
- statsCacheTime = 0
115
- statsCache = null
116
- constructor(config = {}) {
117
- this.config = { ...DEFAULT_CONFIG, ...config }
118
- }
119
-
120
- async getDelay(recipient, content, dedupKey) {
121
- const now = Date.now()
122
- this.cleanup(now)
123
- const contentHash = this.hashContent(dedupKey !== undefined ? dedupKey : content)
124
- const dayMessages = this.messages.filter(m => now - m.timestamp < TIME_CONSTANTS.MS_PER_DAY)
125
- if (dayMessages.length >= this.config.maxPerDay) {
126
- return -1
127
- }
128
- const hourMessages = this.messages.filter(m => now - m.timestamp < TIME_CONSTANTS.MS_PER_HOUR)
129
- if (hourMessages.length >= this.config.maxPerHour) {
130
- hourMessages.sort((a, b) => a.timestamp - b.timestamp)
131
- const oldestInHour = hourMessages[0]
132
- const delay2 = oldestInHour
133
- ? oldestInHour.timestamp + TIME_CONSTANTS.MS_PER_HOUR - now
134
- : TIME_CONSTANTS.MS_PER_HOUR
135
- return Math.max(delay2, TIME_CONSTANTS.MS_PER_MINUTE)
136
- }
137
- const minuteMessages = this.messages.filter(m => now - m.timestamp < TIME_CONSTANTS.MS_PER_MINUTE)
138
- if (minuteMessages.length >= this.config.maxPerMinute) {
139
- minuteMessages.sort((a, b) => a.timestamp - b.timestamp)
140
- const oldestInMinute = minuteMessages[0]
141
- const delay2 = oldestInMinute
142
- ? oldestInMinute.timestamp + TIME_CONSTANTS.MS_PER_MINUTE - now
143
- : TIME_CONSTANTS.MS_PER_MINUTE
144
- return Math.max(delay2, TIME_CONSTANTS.MS_PER_SECOND)
145
- }
146
- const tracker = this.identicalCount.get(contentHash)
147
- if (tracker) {
148
- if (now - tracker.firstSeen < this.config.identicalMessageWindowMs) {
149
- if (tracker.count >= this.config.maxIdenticalMessages) {
150
- return -1
151
- }
152
- }
153
- }
154
- let delay = 0
155
- if (this.burstCount < this.config.burstAllowance) {
156
- this.burstCount++
157
- delay = this.jitter(this.config.minDelayMs * 0.5, this.config.minDelayMs)
158
- } else {
159
- delay = this.jitter(this.config.minDelayMs, this.config.maxDelayMs)
160
- }
161
- const isInterop = recipient.endsWith('@interop')
162
- if (!this.knownChats.has(recipient) || isInterop) {
163
- delay += this.jitter(this.config.newChatDelayMs * 0.5, this.config.newChatDelayMs)
164
- }
165
- const timeSinceLast = now - this.lastMessageTime
166
- if (timeSinceLast < this.config.minDelayMs) {
167
- delay = Math.max(delay, this.config.minDelayMs - timeSinceLast)
168
- }
169
- const typingDelay = Math.min((content?.length || 0) * 15, 2e3)
170
- delay += this.jitter(typingDelay * 0.5, typingDelay)
171
- return Math.round(delay)
172
- }
173
-
174
- record(recipient, content, dedupKey) {
175
- const now = Date.now()
176
- const contentHash = this.hashContent(dedupKey !== undefined ? dedupKey : content)
177
- const timeSinceLast = now - this.lastMessageTime
178
- if (timeSinceLast > TIME_CONSTANTS.BURST_RESET_MS) {
179
- this.burstCount = 0
180
- }
181
- this.messages.push({ timestamp: now, recipient, contentHash })
182
- this.knownChats.add(recipient)
183
- this.lastMessageTime = now
184
- const tracker = this.identicalCount.get(contentHash)
185
- if (tracker) {
186
- if (now - tracker.firstSeen < this.config.identicalMessageWindowMs) {
187
- tracker.count++
188
- tracker.lastSeen = now
189
- } else {
190
- this.identicalCount.set(contentHash, { count: 1, firstSeen: now, lastSeen: now })
191
- }
192
- } else {
193
- this.identicalCount.set(contentHash, { count: 1, firstSeen: now, lastSeen: now })
194
- }
195
- }
196
-
197
- getStats() {
198
- const now = Date.now()
199
- if (this.statsCache && now - this.statsCacheTime < 100) {
200
- return this.statsCache
201
- }
202
- this.cleanup(now)
203
- let lastMinute = 0
204
- let lastHour = 0
205
- let lastDay = 0
206
- for (const m of this.messages) {
207
- const age = now - m.timestamp
208
- if (age < TIME_CONSTANTS.MS_PER_MINUTE) lastMinute++
209
- if (age < TIME_CONSTANTS.MS_PER_HOUR) lastHour++
210
- if (age < TIME_CONSTANTS.MS_PER_DAY) lastDay++
211
- }
212
- this.statsCache = {
213
- lastMinute,
214
- lastHour,
215
- lastDay,
216
- limits: {
217
- perMinute: this.config.maxPerMinute,
218
- perHour: this.config.maxPerHour,
219
- perDay: this.config.maxPerDay
220
- },
221
- knownChats: this.knownChats.size
222
- }
223
- this.statsCacheTime = now
224
- return this.statsCache
225
- }
226
-
227
- getKnownChats() {
228
- return this.knownChats
229
- }
230
-
231
- restoreKnownChats(chats) {
232
- for (const jid of chats) {
233
- this.knownChats.add(jid)
234
- }
235
- }
236
- cleanup(now) {
237
- this.messages = this.messages.filter(m => now - m.timestamp < TIME_CONSTANTS.MS_PER_DAY)
238
- for (const [hash, tracker] of this.identicalCount.entries()) {
239
- if (now - tracker.lastSeen > this.config.identicalMessageWindowMs) {
240
- this.identicalCount.delete(hash)
241
- }
242
- }
243
- }
244
-
245
- jitter(min, max) {
246
- const u1 = Math.random()
247
- const u2 = Math.random()
248
- const normal = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)
249
- const normalized = (normal + 3) / 6
250
- const clamped = Math.max(0, Math.min(1, normalized))
251
- return Math.round(min + clamped * (max - min))
252
- }
253
-
254
- hashContent(content) {
255
- let hash = 0
256
- for (let i = 0; i < content.length; i++) {
257
- const char = content.charCodeAt(i)
258
- hash = (hash << 5) - hash + char
259
- hash |= 0
260
- }
261
- return hash.toString(36)
262
- }
263
- }
264
-
265
-
266
- var DEFAULT_CONFIG2 = {
267
- warmUpDays: 5,
268
- day1Limit: 30,
269
- growthFactor: 1.8,
270
- inactivityThresholdHours: 168
271
- }
272
- var WarmUp = class {
273
- config
274
- state
275
- constructor(config = {}, existingState) {
276
- this.config = { ...DEFAULT_CONFIG2, ...config }
277
- this.state = existingState || this.freshState()
278
- }
279
-
280
- getDailyLimit() {
281
- if (this.state.graduated) return Infinity
282
- const day = this.getCurrentDay()
283
- if (day >= this.config.warmUpDays) {
284
- this.state.graduated = true
285
- return Infinity
286
- }
287
- return Math.round(this.config.day1Limit * Math.pow(this.config.growthFactor, day))
288
- }
289
-
290
- canSend() {
291
- this.checkInactivity()
292
- if (this.state.graduated) return true
293
- const day = this.getCurrentDay()
294
- const todayCount = this.state.dailyCounts[day] || 0
295
- return todayCount < this.getDailyLimit()
296
- }
297
-
298
- record() {
299
- const now = Date.now()
300
- const day = this.getCurrentDay()
301
- while (this.state.dailyCounts.length <= day) {
302
- this.state.dailyCounts.push(0)
303
- }
304
- this.state.dailyCounts[day]++
305
- this.state.lastActiveAt = now
306
- }
307
-
308
- getStatus() {
309
- const day = this.getCurrentDay()
310
- const todaySent = this.state.dailyCounts[day] || 0
311
- const limit = this.getDailyLimit()
312
- return {
313
- phase: this.state.graduated ? 'graduated' : 'warming',
314
- day: Math.min(day + 1, this.config.warmUpDays),
315
- totalDays: this.config.warmUpDays,
316
- todayLimit: limit === Infinity ? -1 : limit,
317
- todaySent,
318
- progress: this.state.graduated ? 100 : Math.round((day / this.config.warmUpDays) * 100)
319
- }
320
- }
321
-
322
- exportState() {
323
- return { ...this.state }
324
- }
325
-
326
- reset() {
327
- this.state = this.freshState()
328
- }
329
- getCurrentDay() {
330
- return Math.floor((Date.now() - this.state.startedAt) / 864e5)
331
- }
332
- checkInactivity() {
333
- const hoursSinceActive = (Date.now() - this.state.lastActiveAt) / 36e5
334
- if (hoursSinceActive > this.config.inactivityThresholdHours && this.state.graduated) {
335
- this.state = this.freshState()
336
- this.state.graduated = false
337
- }
338
- }
339
- freshState() {
340
- const now = Date.now()
341
- return {
342
- startedAt: now,
343
- lastActiveAt: now,
344
- dailyCounts: [],
345
- graduated: false
346
- }
347
- }
348
- }
349
-
350
-
351
- var DEFAULT_CONFIG3 = {
352
- disconnectWarningThreshold: 3,
353
- disconnectCriticalThreshold: 5,
354
- failedMessageThreshold: 5,
355
- autoPauseAt: 'high'
356
- }
357
- var HealthMonitor = class {
358
- config
359
- events = []
360
- startTime = Date.now()
361
- paused = false
362
- lastRisk = 'low'
363
- lastBadEventTime = Date.now()
364
- lastEventWasSevere = false
365
- constructor(config = {}) {
366
- this.config = { ...DEFAULT_CONFIG3, ...config }
367
- }
368
-
369
- recordDisconnect(reason) {
370
- const reasonStr = String(reason)
371
- if (reasonStr === '403' || reasonStr === 'forbidden') {
372
- this.events.push({ type: 'forbidden', timestamp: Date.now(), detail: reasonStr })
373
- this.lastBadEventTime = Date.now()
374
- this.lastEventWasSevere = true
375
- } else if (reasonStr === '401' || reasonStr === 'loggedOut') {
376
- this.events.push({ type: 'loggedOut', timestamp: Date.now(), detail: reasonStr })
377
- this.lastBadEventTime = Date.now()
378
- this.lastEventWasSevere = true
379
- } else if (reasonStr === '463') {
380
- this.events.push({ type: 'reachoutTimelocked', timestamp: Date.now(), detail: reasonStr })
381
- this.lastBadEventTime = Date.now()
382
- this.lastEventWasSevere = false
383
- } else {
384
- this.events.push({ type: 'disconnect', timestamp: Date.now(), detail: reasonStr })
385
- this.lastBadEventTime = Date.now()
386
- this.lastEventWasSevere = false
387
- }
388
- this.checkAndNotify()
389
- }
390
-
391
- recordReconnect() {
392
- this.events.push({ type: 'reconnect', timestamp: Date.now() })
393
- }
394
-
395
- recordMessageFailed(error) {
396
- this.events.push({ type: 'messageFailed', timestamp: Date.now(), detail: error })
397
- this.lastBadEventTime = Date.now()
398
- this.lastEventWasSevere = false
399
- this.checkAndNotify()
400
- }
401
-
402
- recordReachoutTimelock(detail) {
403
- this.events.push({ type: 'reachoutTimelocked', timestamp: Date.now(), detail })
404
- this.lastBadEventTime = Date.now()
405
- this.lastEventWasSevere = false
406
- this.checkAndNotify()
407
- }
408
-
409
- getStatus() {
410
- const now = Date.now()
411
- this.cleanup(now)
412
- const hourEvents = this.events.filter(e => now - e.timestamp < 36e5)
413
- const disconnects = hourEvents.filter(e => e.type === 'disconnect').length
414
- const forbidden = hourEvents.filter(e => e.type === 'forbidden').length
415
- const loggedOut = hourEvents.filter(e => e.type === 'loggedOut').length
416
- const failedMessages = hourEvents.filter(e => e.type === 'messageFailed').length
417
- let score = 0
418
- const reasons = []
419
- if (forbidden > 0) {
420
- score += 40 * forbidden
421
- reasons.push(`${forbidden} forbidden (403) error${forbidden > 1 ? 's' : ''} in last hour`)
422
- }
423
- if (loggedOut > 0) {
424
- score += 60
425
- reasons.push('Logged out by WhatsApp \u2014 possible temporary ban')
426
- }
427
- const timelocked = hourEvents.filter(e => e.type === 'reachoutTimelocked').length
428
- if (timelocked > 0) {
429
- score += 25
430
- reasons.push(`${timelocked} reachout timelock (463) error${timelocked > 1 ? 's' : ''} in last hour`)
431
- }
432
- if (disconnects >= this.config.disconnectCriticalThreshold) {
433
- score += 30
434
- reasons.push(`${disconnects} disconnects in last hour (critical threshold)`)
435
- } else if (disconnects >= this.config.disconnectWarningThreshold) {
436
- score += 30
437
- reasons.push(`${disconnects} disconnects in last hour`)
438
- }
439
- if (failedMessages >= this.config.failedMessageThreshold) {
440
- score += 20
441
- reasons.push(`${failedMessages} failed messages in last hour`)
442
- }
443
- score = Math.min(100, score)
444
- const minutesSinceLastBad = (now - this.lastBadEventTime) / 6e4
445
- const decayRate = this.lastEventWasSevere ? 2 : 5
446
- score = Math.max(0, score - Math.floor(minutesSinceLastBad * decayRate))
447
- let risk
448
- if (score >= 80) risk = 'critical'
449
- else if (score >= 40) risk = 'high'
450
- else if (score >= 15) risk = 'medium'
451
- else risk = 'low'
452
- let recommendation
453
- switch (risk) {
454
- case 'critical':
455
- recommendation = 'STOP ALL MESSAGING IMMEDIATELY. Disconnect and wait 24-48 hours before reconnecting.'
456
- break
457
- case 'high':
458
- recommendation = 'Reduce messaging rate by 80%. Consider pausing for 1-2 hours.'
459
- break
460
- case 'medium':
461
- recommendation = 'Reduce messaging rate by 50%. Increase delays between messages.'
462
- break
463
- default:
464
- recommendation = 'Operating normally. Continue monitoring.'
465
- }
466
- const lastDisconnect = [...this.events]
467
- .reverse()
468
- .find(e => e.type === 'disconnect' || e.type === 'forbidden' || e.type === 'loggedOut')
469
- return {
470
- risk,
471
- score,
472
- reasons: reasons.length ? reasons : ['No issues detected'],
473
- recommendation,
474
- stats: {
475
- disconnectsLastHour: disconnects,
476
- failedMessagesLastHour: failedMessages,
477
- forbiddenErrors: forbidden,
478
- timelockErrors: timelocked,
479
- uptimeMs: now - this.startTime,
480
- lastDisconnectReason: lastDisconnect?.detail
481
- }
482
- }
483
- }
484
-
485
- isPaused() {
486
- if (this.paused) return true
487
- const status = this.getStatus()
488
- const riskOrder = ['low', 'medium', 'high', 'critical']
489
- return riskOrder.indexOf(status.risk) >= riskOrder.indexOf(this.config.autoPauseAt)
490
- }
491
-
492
- setPaused(paused) {
493
- this.paused = paused
494
- }
495
-
496
- reset() {
497
- this.events = []
498
- this.startTime = Date.now()
499
- this.paused = false
500
- this.lastRisk = 'low'
501
- this.lastBadEventTime = Date.now()
502
- this.lastEventWasSevere = false
503
- }
504
- cleanup(now) {
505
- this.events = this.events.filter(e => now - e.timestamp < 216e5)
506
- }
507
- checkAndNotify() {
508
- const status = this.getStatus()
509
- if (status.risk !== this.lastRisk) {
510
- this.lastRisk = status.risk
511
- this.config.onRiskChange?.(status)
512
- }
513
- }
514
- }
515
-
516
-
517
- var DEFAULT_CONFIG4 = {
518
- resumeBufferMs: 1e4
519
- }
520
- var TimelockGuard = class {
521
- config
522
- state = {
523
- isActive: false,
524
- errorCount: 0
525
- }
526
- knownChats = new Set()
527
- resumeTimer = null
528
- timerGeneration = 0
529
-
530
- constructor(config = {}) {
531
- this.config = { ...DEFAULT_CONFIG4, ...config }
532
- }
533
-
534
- onTimelockUpdate(data) {
535
- const wasActive = this.state.isActive
536
- this.state.isActive = !!data.isActive
537
- this.state.enforcementType = data.enforcementType
538
- this.state.expiresAt = data.timeEnforcementEnds
539
- if (this.state.isActive && !wasActive) {
540
- this.state.detectedAt = new Date()
541
- this.state.errorCount = 0
542
- this.config.onTimelockDetected?.(this.getState())
543
- this.scheduleResume()
544
- } else if (this.state.isActive && wasActive) {
545
- this.scheduleResume()
546
- }
547
- if (!this.state.isActive && wasActive) {
548
- this.clearResumeTimer()
549
- this.config.onTimelockLifted?.(this.getState())
550
- }
551
- }
552
-
553
- record463Error() {
554
- this.state.errorCount++
555
- if (!this.state.isActive) {
556
- this.state.isActive = true
557
- this.state.detectedAt = new Date()
558
- this.state.expiresAt = new Date(Date.now() + 6e4)
559
- this.config.onTimelockDetected?.(this.getState())
560
- this.scheduleResume()
561
- }
562
- }
563
-
564
- registerKnownChat(jid) {
565
- this.knownChats.add(jid)
566
- }
567
-
568
- registerKnownChats(jids) {
569
- for (const jid of jids) {
570
- this.knownChats.add(jid)
571
- }
572
- }
573
-
574
- canSend(jid) {
575
- if (!this.state.isActive) {
576
- return { allowed: true }
577
- }
578
- if (this.state.expiresAt) {
579
- const expiryWithBuffer = this.state.expiresAt.getTime() + this.config.resumeBufferMs
580
- if (Date.now() >= expiryWithBuffer) {
581
- this.lift()
582
- return { allowed: true }
583
- }
584
- }
585
- if (jid.endsWith('@g.us') || jid.endsWith('@newsletter')) {
586
- return { allowed: true }
587
- }
588
- if (this.knownChats.has(jid)) {
589
- return { allowed: true }
590
- }
591
- const expiresIn = this.state.expiresAt ? Math.max(0, this.state.expiresAt.getTime() - Date.now()) : 6e4
592
- return {
593
- allowed: false,
594
- reason: `Reachout timelocked (${this.state.enforcementType || 'unknown'}). New contacts blocked. Expires in ${Math.ceil(expiresIn / 1e3)}s.`
595
- }
596
- }
597
-
598
- getState() {
599
- return { ...this.state }
600
- }
601
-
602
- isTimelocked() {
603
- if (!this.state.isActive) return false
604
- if (this.state.expiresAt) {
605
- const expiryWithBuffer = this.state.expiresAt.getTime() + this.config.resumeBufferMs
606
- if (Date.now() >= expiryWithBuffer) {
607
- this.lift()
608
- return false
609
- }
610
- }
611
- return true
612
- }
613
-
614
- getKnownChats() {
615
- return new Set(this.knownChats)
616
- }
617
-
618
- lift() {
619
- if (this.state.isActive) {
620
- this.state.isActive = false
621
- this.clearResumeTimer()
622
- this.config.onTimelockLifted?.(this.getState())
623
- }
624
- }
625
-
626
- reset() {
627
- this.state = { isActive: false, errorCount: 0 }
628
- this.knownChats.clear()
629
- this.clearResumeTimer()
630
- }
631
- scheduleResume() {
632
- this.clearResumeTimer()
633
- if (this.state.expiresAt) {
634
- const delay = this.state.expiresAt.getTime() - Date.now() + this.config.resumeBufferMs
635
- if (delay > 0) {
636
- this.timerGeneration++
637
- const currentGeneration = this.timerGeneration
638
- this.resumeTimer = setTimeout(() => {
639
- if (currentGeneration === this.timerGeneration) {
640
- this.lift()
641
- }
642
- }, delay)
643
- }
644
- }
645
- }
646
- clearResumeTimer() {
647
- if (this.resumeTimer) {
648
- clearTimeout(this.resumeTimer)
649
- this.resumeTimer = null
650
- this.timerGeneration++
651
- }
652
- }
653
- }
654
-
655
-
656
- var DEFAULT_CONFIG5 = {
657
- enabled: false,
658
- minRatio: 0.1,
659
- minMessagesBeforeEnforce: 5,
660
- inboundAutoReplyProbability: 0.25,
661
- autoReplyTemplates: ['\u{1F44D}', '\u{1F44C}', 'ok', 'noted', 'thanks', '\u{1F64F}', 'got it'],
662
- cooldownHoursOnViolation: 24,
663
- scope: 'individual'
664
- }
665
- var ReplyRatioGuard = class {
666
- config
667
- contacts = new Map()
668
- constructor(config = {}) {
669
- this.config = { ...DEFAULT_CONFIG5, ...config }
670
- }
671
-
672
- beforeSend(jid) {
673
- if (!this.config.enabled) {
674
- return { allowed: true }
675
- }
676
- if (this.isGroup(jid) && this.config.scope === 'individual') {
677
- return { allowed: true }
678
- }
679
- const record = this.contacts.get(jid)
680
- if (!record) {
681
- return { allowed: true }
682
- }
683
- if (record.cooledUntil && Date.now() < record.cooledUntil) {
684
- const hoursLeft = Math.ceil((record.cooledUntil - Date.now()) / 36e5)
685
- return {
686
- allowed: false,
687
- reason: `Reply ratio cooldown \u2014 ${record.sent} sent, ${record.received} received. Retry in ${hoursLeft}h`
688
- }
689
- }
690
- if (record.sent >= this.config.minMessagesBeforeEnforce) {
691
- const ratio = record.sent === 0 ? 1 : record.received / record.sent
692
- if (ratio < this.config.minRatio) {
693
- record.cooledUntil = Date.now() + this.config.cooldownHoursOnViolation * 36e5
694
- return {
695
- allowed: false,
696
- reason: `Reply ratio too low (${(ratio * 100).toFixed(1)}% < ${(this.config.minRatio * 100).toFixed(1)}%). Cooldown ${this.config.cooldownHoursOnViolation}h`
697
- }
698
- }
699
- }
700
- return { allowed: true }
701
- }
702
-
703
- recordSent(jid) {
704
- if (!this.config.enabled) return
705
- const record = this.contacts.get(jid) || { sent: 0, received: 0 }
706
- record.sent++
707
- this.contacts.set(jid, record)
708
- }
709
-
710
- recordReceived(jid) {
711
- if (!this.config.enabled) return
712
- const record = this.contacts.get(jid) || { sent: 0, received: 0 }
713
- record.received++
714
- delete record.cooledUntil
715
- this.contacts.set(jid, record)
716
- }
717
-
718
- suggestReply(jid, _msgText) {
719
- if (!this.config.enabled) {
720
- return { shouldReply: false }
721
- }
722
- if (this.isGroup(jid) && this.config.scope === 'individual') {
723
- return { shouldReply: false }
724
- }
725
- if (Math.random() < this.config.inboundAutoReplyProbability) {
726
- const templates = this.config.autoReplyTemplates
727
- const suggestedText = templates[Math.floor(Math.random() * templates.length)]
728
- return { shouldReply: true, suggestedText }
729
- }
730
- return { shouldReply: false }
731
- }
732
-
733
- getStats() {
734
- const perContact = Array.from(this.contacts.entries()).map(([jid, record]) => ({
735
- jid,
736
- sent: record.sent,
737
- received: record.received,
738
- ratio: record.sent === 0 ? 0 : record.received / record.sent,
739
- cooledUntil: record.cooledUntil
740
- }))
741
- const globalSent = perContact.reduce((sum, c) => sum + c.sent, 0)
742
- const globalReceived = perContact.reduce((sum, c) => sum + c.received, 0)
743
- const globalRatio = globalSent === 0 ? 0 : globalReceived / globalSent
744
- const contactsOnCooldown = perContact.filter(c => c.cooledUntil && Date.now() < c.cooledUntil).length
745
- return {
746
- perContact,
747
- globalSent,
748
- globalReceived,
749
- globalRatio,
750
- contactsOnCooldown
751
- }
752
- }
753
-
754
- reset() {
755
- this.contacts.clear()
756
- }
757
-
758
- exportState() {
759
- return {
760
- contacts: Array.from(this.contacts.entries())
761
- }
762
- }
763
-
764
- restoreState(state) {
765
- if (state?.contacts && Array.isArray(state.contacts)) {
766
- this.contacts = new Map(state.contacts)
767
- }
768
- }
769
-
770
- isGroup(jid) {
771
- return jid.endsWith('@g.us')
772
- }
773
- }
774
-
775
-
776
- var DEFAULT_CONFIG6 = {
777
- enabled: false,
778
- requireHandshakeBeforeGroupSend: true,
779
- handshakeMinDelayMs: 36e5,
780
-
781
- groupLurkPeriodMs: 432e5,
782
-
783
- maxStrangerMessagesPerDay: 5,
784
- autoRegisterOnIncoming: true
785
- }
786
- var ContactGraphWarmer = class {
787
- config
788
- contacts = new Map()
789
- groups = new Map()
790
- strangerMessagesToday = 0
791
- lastStrangerResetDay = this.getCurrentDay()
792
- constructor(config = {}) {
793
- this.config = { ...DEFAULT_CONFIG6, ...config }
794
- }
795
-
796
- canMessage(jid) {
797
- if (!this.config.enabled) {
798
- return { allowed: true }
799
- }
800
- const currentDay = this.getCurrentDay()
801
- if (currentDay !== this.lastStrangerResetDay) {
802
- this.strangerMessagesToday = 0
803
- this.lastStrangerResetDay = currentDay
804
- }
805
- if (this.isGroup(jid)) {
806
- return this.checkGroupMessage(jid)
807
- }
808
- return this.checkIndividualMessage(jid)
809
- }
810
-
811
- markHandshakeSent(jid) {
812
- if (!this.config.enabled) return
813
- if (this.isGroup(jid)) return
814
- const record = this.contacts.get(jid) || { state: 'stranger' }
815
- record.state = 'handshake_sent'
816
- record.handshakeSentAt = Date.now()
817
- this.contacts.set(jid, record)
818
- }
819
-
820
- markHandshakeComplete(jid) {
821
- if (!this.config.enabled) return
822
- if (this.isGroup(jid)) return
823
- const record = this.contacts.get(jid) || { state: 'stranger' }
824
- record.state = 'handshake_complete'
825
- this.contacts.set(jid, record)
826
- }
827
-
828
- registerKnownContact(jid) {
829
- if (!this.config.enabled) return
830
- if (this.isGroup(jid)) return
831
- const record = this.contacts.get(jid) || { state: 'stranger' }
832
- record.state = 'known'
833
- this.contacts.set(jid, record)
834
- }
835
-
836
- registerGroupJoin(groupJid) {
837
- if (!this.config.enabled) return
838
- if (!this.isGroup(groupJid)) return
839
- this.groups.set(groupJid, { joinedAt: Date.now() })
840
- }
841
-
842
- getContactState(jid) {
843
- if (this.isGroup(jid)) return 'known'
844
- return this.contacts.get(jid)?.state || 'stranger'
845
- }
846
-
847
- onIncomingMessage(jid) {
848
- if (!this.config.enabled) return
849
- if (this.isGroup(jid)) return
850
- if (this.config.autoRegisterOnIncoming) {
851
- this.registerKnownContact(jid)
852
- }
853
- }
854
-
855
- getStats() {
856
- const knownContacts = Array.from(this.contacts.values()).filter(c => c.state === 'known').length
857
- const pendingHandshakes = Array.from(this.contacts.values()).filter(c => c.state === 'handshake_sent').length
858
- const groupsJoined = Array.from(this.groups.entries()).map(([groupJid, record]) => ({
859
- groupJid,
860
- joinedAt: record.joinedAt,
861
- firstSendUnlocksAt: record.joinedAt + this.config.groupLurkPeriodMs
862
- }))
863
- return {
864
- knownContacts,
865
- pendingHandshakes,
866
- strangersToday: this.strangerMessagesToday,
867
- groupsJoined
868
- }
869
- }
870
-
871
- reset() {
872
- this.contacts.clear()
873
- this.groups.clear()
874
- this.strangerMessagesToday = 0
875
- this.lastStrangerResetDay = this.getCurrentDay()
876
- }
877
-
878
- exportState() {
879
- return {
880
- contacts: Array.from(this.contacts.entries()),
881
- groups: Array.from(this.groups.entries()),
882
- strangerMessagesToday: this.strangerMessagesToday,
883
- lastStrangerResetDay: this.lastStrangerResetDay
884
- }
885
- }
886
-
887
- restoreState(state) {
888
- if (state?.contacts && Array.isArray(state.contacts)) {
889
- this.contacts = new Map(state.contacts)
890
- }
891
- if (state?.groups && Array.isArray(state.groups)) {
892
- this.groups = new Map(state.groups)
893
- }
894
- if (typeof state?.strangerMessagesToday === 'number') {
895
- this.strangerMessagesToday = state.strangerMessagesToday
896
- }
897
- if (typeof state?.lastStrangerResetDay === 'number') {
898
- this.lastStrangerResetDay = state.lastStrangerResetDay
899
- }
900
- }
901
-
902
- isGroup(jid) {
903
- return jid.endsWith('@g.us')
904
- }
905
- getCurrentDay() {
906
- return Math.floor(Date.now() / 864e5)
907
- }
908
- checkGroupMessage(groupJid) {
909
- const record = this.groups.get(groupJid)
910
- if (!record) {
911
- return { allowed: true }
912
- }
913
- const lurkEndsAt = record.joinedAt + this.config.groupLurkPeriodMs
914
- if (Date.now() < lurkEndsAt) {
915
- const minutesLeft = Math.ceil((lurkEndsAt - Date.now()) / 6e4)
916
- return {
917
- allowed: false,
918
- reason: `Group lurk period not elapsed \u2014 wait ${minutesLeft} minutes`
919
- }
920
- }
921
- return { allowed: true }
922
- }
923
- checkIndividualMessage(jid) {
924
- const record = this.contacts.get(jid)
925
- if (!record || record.state === 'stranger') {
926
- if (this.config.requireHandshakeBeforeGroupSend) {
927
- if (this.strangerMessagesToday >= this.config.maxStrangerMessagesPerDay) {
928
- return {
929
- allowed: false,
930
- reason: `Daily new-contact limit reached (${this.config.maxStrangerMessagesPerDay})`,
931
- needsHandshake: true
932
- }
933
- }
934
- this.strangerMessagesToday++
935
- }
936
- return { allowed: true, needsHandshake: true }
937
- }
938
- if (record.state === 'handshake_sent') {
939
- if (!record.handshakeSentAt) {
940
- return { allowed: true }
941
- }
942
- const elapsed = Date.now() - record.handshakeSentAt
943
- if (elapsed < this.config.handshakeMinDelayMs) {
944
- const minutesLeft = Math.ceil((this.config.handshakeMinDelayMs - elapsed) / 6e4)
945
- return {
946
- allowed: false,
947
- reason: `Handshake too recent \u2014 wait ${minutesLeft} minutes`
948
- }
949
- }
950
- }
951
- return { allowed: true }
952
- }
953
- }
954
-
955
-
956
- var DEFAULT_CONFIG7 = {
957
- enabled: false,
958
- enableCircadianRhythm: true,
959
- timezone: 'UTC',
960
- activityCurve: 'office',
961
- circadian: {
962
- enabled: true,
963
- profile: 'default',
964
- timezone: 'UTC'
965
- },
966
- distractionPauseProbability: 0.05,
967
- distractionPauseMinMs: 3e5,
968
- distractionPauseMaxMs: 12e5,
969
- readReceiptDelayMinMs: 3e3,
970
- readReceiptDelayMaxMs: 45e3,
971
- readReceiptSkipProbability: 0.15,
972
- offlineGapProbability: 0.03,
973
- offlineGapMinMs: 3e5,
974
- offlineGapMaxMs: 9e5,
975
- enableTypingModel: true,
976
- typingWPM: 45,
977
- typingWPMStdDev: 15,
978
- thinkPauseProbability: 0.08,
979
- thinkPauseMinMs: 800,
980
- thinkPauseMaxMs: 3500,
981
- intermittentPausedProbability: 0.4,
982
- typingMaxMs: 9e4,
983
- typingMinMs: 600
984
- }
985
- var ACTIVITY_CURVES = {
986
- office: [
987
- 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
988
-
989
- 0.5, 0.5,
990
-
991
- 0.95, 0.95,
992
-
993
- 0.6,
994
-
995
- 0.9, 0.9, 0.9, 0.9,
996
-
997
- 0.6, 0.6,
998
-
999
- 0.4, 0.4,
1000
-
1001
- 0.2, 0.2, 0.2, 0.2
1002
-
1003
- ],
1004
- social: [
1005
- 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
1006
-
1007
- 0.3, 0.4,
1008
-
1009
- 0.7, 0.8,
1010
-
1011
- 0.5,
1012
-
1013
- 0.7, 0.7,
1014
-
1015
- 0.4,
1016
-
1017
- 0.8, 0.9, 0.9,
1018
-
1019
- 0.6,
1020
-
1021
- 0.8, 0.85, 0.9, 0.95, 1
1022
-
1023
- ],
1024
- global: [
1025
- 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
1026
-
1027
- 0.4, 0.4,
1028
-
1029
- 0.6, 0.7, 0.8, 0.8,
1030
-
1031
- 0.6,
1032
-
1033
- 0.8, 0.8, 0.8, 0.8,
1034
-
1035
- 0.7, 0.7,
1036
-
1037
- 0.6, 0.5, 0.5, 0.5, 0.5, 0.5
1038
-
1039
- ]
1040
- }
1041
- function getCircadianMultiplier(date = new Date(), profile = 'default', timezone) {
1042
- if (profile === 'always_on') {
1043
- return 1
1044
- }
1045
- let hour
1046
- if (timezone) {
1047
- try {
1048
- const formatter = new Intl.DateTimeFormat('en-US', {
1049
- timeZone: timezone,
1050
- hour: 'numeric',
1051
- hour12: false
1052
- })
1053
- const parts = formatter.formatToParts(date)
1054
- const hourPart = parts.find(p => p.type === 'hour')
1055
- hour = hourPart ? parseInt(hourPart.value, 10) : date.getHours()
1056
- } catch {
1057
- hour = date.getHours()
1058
- }
1059
- } else {
1060
- hour = date.getHours()
1061
- }
1062
- let shiftedHour = hour
1063
- if (profile === 'nightOwl') {
1064
- shiftedHour = (hour - 3 + 24) % 24
1065
- } else if (profile === 'earlyBird') {
1066
- shiftedHour = (hour + 2) % 24
1067
- }
1068
- if (shiftedHour >= 9 && shiftedHour < 22) {
1069
- const t = (shiftedHour - 9) / 13
1070
- return 1 + 0.2 * Math.cos(2 * Math.PI * t)
1071
- } else if (shiftedHour >= 22 && shiftedHour < 24) {
1072
- const t = (shiftedHour - 22) / 2
1073
- return 1.2 + 1.3 * t
1074
- } else if (shiftedHour >= 0 && shiftedHour < 2) {
1075
- const t = shiftedHour / 2
1076
- return 2.5 + 1.5 * t
1077
- } else if (shiftedHour >= 2 && shiftedHour < 6) {
1078
- const t = (shiftedHour - 2) / 4
1079
- return 5 + 1 * Math.cos(Math.PI * t)
1080
- } else {
1081
- const t = (shiftedHour - 6) / 3
1082
- return 4 - 3 * t
1083
- }
1084
- }
1085
- var PresenceChoreographer = class {
1086
- config
1087
- stats = {
1088
- distractionPausesInjected: 0,
1089
- offlineGapsInjected: 0,
1090
- readReceiptsDelayed: 0,
1091
- readReceiptsSkipped: 0,
1092
- typingPlansComputed: 0,
1093
- typingPlansExecuted: 0,
1094
- totalTypingTimeMs: 0
1095
- }
1096
- constructor(config = {}) {
1097
- this.config = {
1098
- ...DEFAULT_CONFIG7,
1099
- ...config,
1100
- circadian: {
1101
- ...DEFAULT_CONFIG7.circadian,
1102
- ...(config.circadian || {})
1103
- }
1104
- }
1105
- }
1106
-
1107
- getCurrentActivityFactor() {
1108
- if (!this.config.enabled || !this.config.enableCircadianRhythm) {
1109
- return 1
1110
- }
1111
- const hour = this.getLocalHour()
1112
- const curve = ACTIVITY_CURVES[this.config.activityCurve]
1113
- return curve[hour] || 0.5
1114
- }
1115
-
1116
- shouldPauseForDistraction() {
1117
- if (!this.config.enabled) {
1118
- return { pause: false, durationMs: 0 }
1119
- }
1120
- if (Math.random() < this.config.distractionPauseProbability) {
1121
- const durationMs = this.randomBetween(this.config.distractionPauseMinMs, this.config.distractionPauseMaxMs)
1122
- this.stats.distractionPausesInjected++
1123
- return { pause: true, durationMs }
1124
- }
1125
- return { pause: false, durationMs: 0 }
1126
- }
1127
-
1128
- shouldTakeOfflineGap() {
1129
- if (!this.config.enabled) {
1130
- return { offline: false, durationMs: 0 }
1131
- }
1132
- if (Math.random() < this.config.offlineGapProbability) {
1133
- const durationMs = this.randomBetween(this.config.offlineGapMinMs, this.config.offlineGapMaxMs)
1134
- this.stats.offlineGapsInjected++
1135
- return { offline: true, durationMs }
1136
- }
1137
- return { offline: false, durationMs: 0 }
1138
- }
1139
-
1140
- shouldMarkRead() {
1141
- if (!this.config.enabled) {
1142
- return { mark: true, delayMs: 0 }
1143
- }
1144
- if (Math.random() < this.config.readReceiptSkipProbability) {
1145
- this.stats.readReceiptsSkipped++
1146
- return { mark: false, delayMs: 0 }
1147
- }
1148
- const baseDelayMs = this.randomBetween(this.config.readReceiptDelayMinMs, this.config.readReceiptDelayMaxMs)
1149
- let delayMs = baseDelayMs
1150
- if (this.config.circadian.enabled) {
1151
- const circadianMultiplier = getCircadianMultiplier(
1152
- new Date(),
1153
- this.config.circadian.profile,
1154
- this.config.circadian.timezone
1155
- )
1156
- delayMs = Math.floor(baseDelayMs * circadianMultiplier)
1157
- }
1158
- this.stats.readReceiptsDelayed++
1159
- return { mark: true, delayMs }
1160
- }
1161
-
1162
- computeTypingPlan(messageLength) {
1163
- if (!this.config.enabled || !this.config.enableTypingModel) {
1164
- return [{ state: 'composing', durationMs: this.config.typingMinMs }]
1165
- }
1166
- this.stats.typingPlansComputed++
1167
- if (messageLength === 0) {
1168
- return [{ state: 'composing', durationMs: this.config.typingMinMs }]
1169
- }
1170
- const wpmSample = this.clamp(this.gaussianSample(this.config.typingWPM, this.config.typingWPMStdDev), 10, 120)
1171
- const cps = (wpmSample * 5) / 60
1172
- const baseMs = (messageLength / cps) * 1e3
1173
- let circadianMultiplier = 1
1174
- if (this.config.circadian.enabled) {
1175
- circadianMultiplier = getCircadianMultiplier(
1176
- new Date(),
1177
- this.config.circadian.profile,
1178
- this.config.circadian.timezone
1179
- )
1180
- }
1181
- const targetMs = this.clamp(baseMs * circadianMultiplier, this.config.typingMinMs, this.config.typingMaxMs)
1182
- const plan = []
1183
- let remainingBudget = targetMs
1184
- let position = 0
1185
- const chunkSize = 10
1186
- const numChunks = Math.max(1, Math.ceil(messageLength / chunkSize))
1187
- for (let i = 0; i < numChunks && remainingBudget > 0; i++) {
1188
- const charsInChunk = Math.min(chunkSize, messageLength - position)
1189
- const remainingChunks = numChunks - i
1190
- const chunkBudget = remainingBudget / remainingChunks
1191
- const chunkTypingMs = Math.floor(Math.min(chunkBudget, remainingBudget))
1192
- if (chunkTypingMs <= 0) break
1193
- if (i > 0 && i < numChunks - 1 && Math.random() < this.config.thinkPauseProbability) {
1194
- plan.push({ state: 'composing', durationMs: chunkTypingMs })
1195
- remainingBudget -= chunkTypingMs
1196
- const basePauseMs = this.randomBetween(this.config.thinkPauseMinMs, this.config.thinkPauseMaxMs)
1197
- const pauseMs = Math.floor(basePauseMs * circadianMultiplier)
1198
- plan.push({ state: 'paused', durationMs: pauseMs })
1199
- } else {
1200
- if (plan.length === 0 || plan[plan.length - 1].state === 'paused') {
1201
- plan.push({ state: 'composing', durationMs: chunkTypingMs })
1202
- } else {
1203
- plan[plan.length - 1].durationMs += chunkTypingMs
1204
- }
1205
- remainingBudget -= chunkTypingMs
1206
- }
1207
- position += charsInChunk
1208
- }
1209
- if (Math.random() < this.config.intermittentPausedProbability) {
1210
- const baseFinalPauseMs = this.randomBetween(200, 800)
1211
- const finalPauseMs = Math.floor(baseFinalPauseMs * circadianMultiplier)
1212
- plan.push({ state: 'paused', durationMs: finalPauseMs })
1213
- }
1214
- if (plan.length === 0 || !plan.some(step => step.state === 'composing')) {
1215
- return [{ state: 'composing', durationMs: this.config.typingMinMs }]
1216
- }
1217
- return plan
1218
- }
1219
-
1220
- async executeTypingPlan(sock, jid, plan, options) {
1221
- this.stats.typingPlansExecuted++
1222
- for (const step of plan) {
1223
- if (options?.signal?.aborted) {
1224
- await Promise.resolve(sock.sendPresenceUpdate('paused', jid))
1225
- throw new Error('Typing plan aborted')
1226
- }
1227
- await Promise.resolve(sock.sendPresenceUpdate(step.state, jid))
1228
- await this.sleep(step.durationMs)
1229
- this.stats.totalTypingTimeMs += step.durationMs
1230
- }
1231
- }
1232
-
1233
- getStats() {
1234
- return {
1235
- currentActivityFactor: this.getCurrentActivityFactor(),
1236
- distractionPausesInjected: this.stats.distractionPausesInjected,
1237
- offlineGapsInjected: this.stats.offlineGapsInjected,
1238
- readReceiptsDelayed: this.stats.readReceiptsDelayed,
1239
- readReceiptsSkipped: this.stats.readReceiptsSkipped,
1240
- currentHourLocal: this.getLocalHour(),
1241
- typingPlansComputed: this.stats.typingPlansComputed,
1242
- typingPlansExecuted: this.stats.typingPlansExecuted,
1243
- totalTypingTimeMs: this.stats.totalTypingTimeMs
1244
- }
1245
- }
1246
-
1247
- reset() {
1248
- this.stats = {
1249
- distractionPausesInjected: 0,
1250
- offlineGapsInjected: 0,
1251
- readReceiptsDelayed: 0,
1252
- readReceiptsSkipped: 0,
1253
- typingPlansComputed: 0,
1254
- typingPlansExecuted: 0,
1255
- totalTypingTimeMs: 0
1256
- }
1257
- }
1258
-
1259
- getLocalHour() {
1260
- try {
1261
- const formatter = new Intl.DateTimeFormat('en-US', {
1262
- timeZone: this.config.timezone,
1263
- hour: 'numeric',
1264
- hour12: false
1265
- })
1266
- const parts = formatter.formatToParts( new Date())
1267
- const hourPart = parts.find(p => p.type === 'hour')
1268
- if (hourPart) {
1269
- return parseInt(hourPart.value, 10)
1270
- }
1271
- } catch (error) {}
1272
- return new Date().getUTCHours()
1273
- }
1274
- randomBetween(min, max) {
1275
- return Math.floor(Math.random() * (max - min + 1)) + min
1276
- }
1277
- clamp(value, min, max) {
1278
- return Math.max(min, Math.min(max, value))
1279
- }
1280
-
1281
- gaussianSample(mean, stdDev) {
1282
- const u1 = Math.random()
1283
- const u2 = Math.random()
1284
- const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)
1285
- return mean + z0 * stdDev
1286
- }
1287
- sleep(ms) {
1288
- return new Promise(resolve => setTimeout(resolve, ms))
1289
- }
1290
- }
1291
-
1292
-
1293
- var DEFAULT_CONFIG8 = {
1294
- enabled: false,
1295
- maxRetries: 5,
1296
- spiralThreshold: 3,
1297
- onSpiral: () => {}
1298
- }
1299
- var RetryReasonTracker = class {
1300
- config
1301
- retries = new Map()
1302
- totalRetries = 0
1303
- reasonCounts = {
1304
- no_session: 0,
1305
- invalid_key: 0,
1306
- bad_mac: 0,
1307
- decryption_failure: 0,
1308
- server_error_463: 0,
1309
- server_error_429: 0,
1310
- timeout: 0,
1311
- no_route: 0,
1312
- node_malformed: 0,
1313
- unknown: 0
1314
- }
1315
- spiralsDetected = 0
1316
- constructor(config) {
1317
- this.config = { ...DEFAULT_CONFIG8, ...config }
1318
- }
1319
-
1320
- onMessageUpdate(update) {
1321
- if (!this.config.enabled) return
1322
- const msgId = update.key?.id
1323
- if (!msgId) return
1324
- if (update.status !== 0 && !update.error) return
1325
- const reason = this.classify(update.error || update)
1326
- this.recordRetry(msgId, reason)
1327
- }
1328
-
1329
- classify(err) {
1330
- if (!err) return 'unknown'
1331
- const statusCode = err.output?.statusCode || err.statusCode || err.status
1332
- if (statusCode === 463) return 'server_error_463'
1333
- if (statusCode === 429) return 'server_error_429'
1334
- const errorMsg = (err.message || err.text || String(err)).toLowerCase()
1335
- if (errorMsg.includes('bad mac')) return 'bad_mac'
1336
- if (errorMsg.includes('no session') || errorMsg.includes('session not found')) return 'no_session'
1337
- if (errorMsg.includes('invalid key') || errorMsg.includes('key error')) return 'invalid_key'
1338
- if (errorMsg.includes('decryption') || errorMsg.includes('decrypt')) return 'decryption_failure'
1339
- if (errorMsg.includes('timeout') || errorMsg.includes('timed out')) return 'timeout'
1340
- if (errorMsg.includes('no route') || errorMsg.includes('unreachable') || errorMsg.includes('offline'))
1341
- return 'no_route'
1342
- if (errorMsg.includes('malformed') || errorMsg.includes('invalid node')) return 'node_malformed'
1343
- return 'unknown'
1344
- }
1345
-
1346
- recordRetry(msgId, reason) {
1347
- const now = Date.now()
1348
- let record = this.retries.get(msgId)
1349
- if (!record) {
1350
- record = {
1351
- msgId,
1352
- count: 0,
1353
- reasons: [],
1354
- firstRetry: now,
1355
- lastRetry: now
1356
- }
1357
- this.retries.set(msgId, record)
1358
- }
1359
- record.count++
1360
- record.reasons.push(reason)
1361
- record.lastRetry = now
1362
- this.totalRetries++
1363
- this.reasonCounts[reason]++
1364
- if (record.count >= this.config.spiralThreshold) {
1365
- this.spiralsDetected++
1366
- this.config.onSpiral(msgId, reason)
1367
- }
1368
- }
1369
-
1370
- isSpiraling(msgId) {
1371
- const record = this.retries.get(msgId)
1372
- return record ? record.count >= this.config.spiralThreshold : false
1373
- }
1374
-
1375
- clear(msgId) {
1376
- this.retries.delete(msgId)
1377
- }
1378
-
1379
- getStats() {
1380
- return {
1381
- totalRetries: this.totalRetries,
1382
- byReason: { ...this.reasonCounts },
1383
- spiralsDetected: this.spiralsDetected,
1384
- activeRetries: this.retries.size
1385
- }
1386
- }
1387
-
1388
- cleanup() {
1389
- const now = Date.now()
1390
- const maxAge = 5 * 60 * 1e3
1391
- for (const [msgId, record] of this.retries.entries()) {
1392
- if (now - record.lastRetry > maxAge) {
1393
- this.retries.delete(msgId)
1394
- }
1395
- }
1396
- }
1397
-
1398
- destroy() {
1399
- this.retries.clear()
1400
- this.cleanup()
1401
- }
1402
- }
1403
-
1404
-
1405
- var DEFAULT_CONFIG9 = {
1406
- enabled: false,
1407
- rampDurationMs: 6e4,
1408
- initialRateMultiplier: 0.1,
1409
- rampSteps: 6,
1410
- baselineRatePerMinute: null
1411
- }
1412
- var PostReconnectThrottle = class {
1413
- config
1414
- throttledSince = null
1415
- throttledSendCount = 0
1416
- lifetimeReconnects = 0
1417
- rampTimer = null
1418
- currentStep = 0
1419
-
1420
- sendsInCurrentWindow = 0
1421
- currentWindowStart = 0
1422
- WINDOW_DURATION_MS = 6e4
1423
-
1424
- constructor(config) {
1425
- this.config = {
1426
- ...DEFAULT_CONFIG9,
1427
- ...config,
1428
- baselineRatePerMinute: config?.baselineRatePerMinute || null
1429
- }
1430
- }
1431
-
1432
- onReconnect() {
1433
- if (!this.config.enabled) return
1434
- this.throttledSince = Date.now()
1435
- this.currentStep = 0
1436
- this.throttledSendCount = 0
1437
- this.lifetimeReconnects++
1438
- this.sendsInCurrentWindow = 0
1439
- this.currentWindowStart = Date.now()
1440
- if (this.rampTimer) {
1441
- clearTimeout(this.rampTimer)
1442
- }
1443
- this.scheduleNextRampStep()
1444
- }
1445
-
1446
- onDisconnect() {}
1447
-
1448
- scheduleNextRampStep() {
1449
- if (this.currentStep >= this.config.rampSteps) {
1450
- this.throttledSince = null
1451
- this.rampTimer = null
1452
- return
1453
- }
1454
- const stepDuration = this.config.rampDurationMs / this.config.rampSteps
1455
- this.rampTimer = setTimeout(() => {
1456
- this.currentStep++
1457
- this.scheduleNextRampStep()
1458
- }, stepDuration)
1459
- }
1460
-
1461
- getCurrentMultiplier() {
1462
- if (!this.config.enabled || !this.throttledSince) {
1463
- return 1
1464
- }
1465
- const elapsed = Date.now() - this.throttledSince
1466
- if (elapsed >= this.config.rampDurationMs) {
1467
- return 1
1468
- }
1469
- const progress = this.currentStep / this.config.rampSteps
1470
- const multiplier = this.config.initialRateMultiplier + (1 - this.config.initialRateMultiplier) * progress
1471
- return Math.min(1, multiplier)
1472
- }
1473
-
1474
- beforeSend() {
1475
- if (!this.config.enabled || !this.throttledSince) {
1476
- return { allowed: true }
1477
- }
1478
- const now = Date.now()
1479
- const multiplier = this.getCurrentMultiplier()
1480
- if (multiplier >= 1) {
1481
- this.throttledSince = null
1482
- return { allowed: true }
1483
- }
1484
- if (now - this.currentWindowStart >= this.WINDOW_DURATION_MS) {
1485
- this.sendsInCurrentWindow = 0
1486
- this.currentWindowStart = now
1487
- }
1488
- const baselineRate = this.config.baselineRatePerMinute ? this.config.baselineRatePerMinute() : 8
1489
- const allowedInWindow = Math.max(1, Math.floor(baselineRate * multiplier))
1490
- if (this.sendsInCurrentWindow >= allowedInWindow) {
1491
- const windowRemaining = this.WINDOW_DURATION_MS - (now - this.currentWindowStart)
1492
- return {
1493
- allowed: false,
1494
- reason: `Post-reconnect throttle: ${Math.floor(multiplier * 100)}% rate (${this.sendsInCurrentWindow}/${allowedInWindow} sends in window)`,
1495
- retryAfterMs: windowRemaining
1496
- }
1497
- }
1498
- this.sendsInCurrentWindow++
1499
- this.throttledSendCount++
1500
- return { allowed: true }
1501
- }
1502
-
1503
- getStats() {
1504
- const multiplier = this.getCurrentMultiplier()
1505
- const isThrottled = this.throttledSince !== null && multiplier < 1
1506
- const remainingMs =
1507
- isThrottled && this.throttledSince
1508
- ? Math.max(0, this.config.rampDurationMs - (Date.now() - this.throttledSince))
1509
- : 0
1510
- return {
1511
- isThrottled,
1512
- currentMultiplier: multiplier,
1513
- throttledSinceMs: this.throttledSince,
1514
- remainingMs,
1515
- throttledSendCount: this.throttledSendCount,
1516
- lifetimeReconnects: this.lifetimeReconnects
1517
- }
1518
- }
1519
-
1520
- destroy() {
1521
- if (this.rampTimer) {
1522
- clearTimeout(this.rampTimer)
1523
- this.rampTimer = null
1524
- }
1525
- this.throttledSince = null
1526
- }
1527
- }
1528
-
1529
-
1530
- var DEFAULT_CONFIG10 = {
1531
- canonical: 'pn',
1532
- maxEntries: 1e4
1533
- }
1534
- var LidResolver = class {
1535
- config
1536
- persistence
1537
-
1538
- lidToPn = new Map()
1539
- pnToLid = new Map()
1540
-
1541
- stats = {
1542
- learnedFromEvents: 0,
1543
- lookupsServed: 0,
1544
- lookupMisses: 0
1545
- }
1546
- constructor(config = {}) {
1547
- this.config = { ...DEFAULT_CONFIG10, ...config }
1548
- this.persistence = config.persistence
1549
- if (this.persistence?.load) {
1550
- void this.hydrate()
1551
- }
1552
- }
1553
-
1554
- learn(mapping) {
1555
- let lid = mapping.lid ? this.normalizeJid(mapping.lid) : void 0
1556
- let pn = mapping.pn ? this.normalizeJid(mapping.pn) : void 0
1557
- const phone = mapping.phone
1558
- if (!lid || (!pn && !phone)) {
1559
- return
1560
- }
1561
- if (!pn && phone) {
1562
- pn = `${phone}@s.whatsapp.net`
1563
- }
1564
- if (!lid || !pn) return
1565
- if (!lid.endsWith('@lid')) return
1566
- if (!pn.endsWith('@s.whatsapp.net')) return
1567
- const existing = this.lidToPn.get(lid)
1568
- if (existing) {
1569
- existing.seenCount++
1570
- existing.learnedAt = Date.now()
1571
- return
1572
- }
1573
- const extractedPhone = phone || pn.split('@')[0]
1574
- const newMapping = {
1575
- lid,
1576
- pn,
1577
- phone: extractedPhone,
1578
- learnedAt: Date.now(),
1579
- seenCount: 1
1580
- }
1581
- if (this.lidToPn.size >= this.config.maxEntries) {
1582
- this.evictLRU()
1583
- }
1584
- this.lidToPn.set(lid, newMapping)
1585
- this.pnToLid.set(pn, lid)
1586
- this.stats.learnedFromEvents++
1587
- if (this.persistence?.save) {
1588
- void this.flush()
1589
- }
1590
- }
1591
-
1592
- resolveCanonical(jid) {
1593
- const normalized = this.normalizeJid(jid)
1594
- if (this.config.canonical === 'pn') {
1595
- if (normalized.endsWith('@lid')) {
1596
- const mapping = this.lidToPn.get(normalized)
1597
- if (mapping) {
1598
- this.stats.lookupsServed++
1599
- mapping.learnedAt = Date.now()
1600
- return mapping.pn
1601
- }
1602
- this.stats.lookupMisses++
1603
- return jid
1604
- }
1605
- this.stats.lookupsServed++
1606
- return normalized
1607
- } else {
1608
- if (normalized.endsWith('@s.whatsapp.net')) {
1609
- const lid = this.pnToLid.get(normalized)
1610
- if (lid) {
1611
- this.stats.lookupsServed++
1612
- const mapping = this.lidToPn.get(lid)
1613
- if (mapping) {
1614
- mapping.learnedAt = Date.now()
1615
- }
1616
- return lid
1617
- }
1618
- this.stats.lookupMisses++
1619
- return jid
1620
- }
1621
- this.stats.lookupsServed++
1622
- return normalized
1623
- }
1624
- }
1625
-
1626
- getLid(pn) {
1627
- const normalized = this.normalizeJid(pn)
1628
- const lid = this.pnToLid.get(normalized)
1629
- if (lid) {
1630
- const mapping = this.lidToPn.get(lid)
1631
- if (mapping) {
1632
- mapping.learnedAt = Date.now()
1633
- }
1634
- }
1635
- return lid || null
1636
- }
1637
- getPn(lid) {
1638
- const normalized = this.normalizeJid(lid)
1639
- const mapping = this.lidToPn.get(normalized)
1640
- if (mapping) {
1641
- mapping.learnedAt = Date.now()
1642
- return mapping.pn
1643
- }
1644
- return null
1645
- }
1646
-
1647
- getMapping(jid) {
1648
- const normalized = this.normalizeJid(jid)
1649
- const byLid = this.lidToPn.get(normalized)
1650
- if (byLid) {
1651
- byLid.learnedAt = Date.now()
1652
- return byLid
1653
- }
1654
- const lid = this.pnToLid.get(normalized)
1655
- if (lid) {
1656
- const mapping = this.lidToPn.get(lid)
1657
- if (mapping) {
1658
- mapping.learnedAt = Date.now()
1659
- return mapping
1660
- }
1661
- }
1662
- return null
1663
- }
1664
-
1665
- async hydrate() {
1666
- if (!this.persistence?.load) return
1667
- try {
1668
- const stored = await this.persistence.load()
1669
- if (!stored || typeof stored !== 'object') return
1670
- for (const [lid, serialized] of Object.entries(stored)) {
1671
- if (typeof serialized === 'string') {
1672
- const pn = serialized
1673
- const phone = pn.split('@')[0]
1674
- const mapping = {
1675
- lid,
1676
- pn,
1677
- phone,
1678
- learnedAt: Date.now(),
1679
- seenCount: 1
1680
- }
1681
- this.lidToPn.set(lid, mapping)
1682
- this.pnToLid.set(pn, lid)
1683
- } else if (typeof serialized === 'object' && serialized !== null) {
1684
- const mapping = serialized
1685
- this.lidToPn.set(lid, mapping)
1686
- this.pnToLid.set(mapping.pn, lid)
1687
- }
1688
- }
1689
- } catch (error) {}
1690
- }
1691
-
1692
- async flush() {
1693
- if (!this.persistence?.save) return
1694
- try {
1695
- const toStore = {}
1696
- for (const [lid, mapping] of this.lidToPn.entries()) {
1697
- toStore[lid] = mapping
1698
- }
1699
- await this.persistence.save(toStore)
1700
- } catch (error) {}
1701
- }
1702
- getStats() {
1703
- return {
1704
- totalMappings: this.lidToPn.size,
1705
- learnedFromEvents: this.stats.learnedFromEvents,
1706
- lookupsServed: this.stats.lookupsServed,
1707
- lookupMisses: this.stats.lookupMisses,
1708
- canonicalForm: this.config.canonical
1709
- }
1710
- }
1711
-
1712
- reset() {
1713
- this.lidToPn.clear()
1714
- this.pnToLid.clear()
1715
- this.stats = {
1716
- learnedFromEvents: 0,
1717
- lookupsServed: 0,
1718
- lookupMisses: 0
1719
- }
1720
- }
1721
- destroy() {
1722
- this.reset()
1723
- if (this.persistence?.save) {
1724
- void this.flush()
1725
- }
1726
- }
1727
-
1728
-
1729
- normalizeJid(jid) {
1730
- return jid.replace(/:\d+@/, '@')
1731
- }
1732
-
1733
- evictLRU() {
1734
- let oldestLid = null
1735
- let oldestTime = Infinity
1736
- for (const [lid, mapping] of this.lidToPn.entries()) {
1737
- if (mapping.learnedAt < oldestTime) {
1738
- oldestTime = mapping.learnedAt
1739
- oldestLid = lid
1740
- }
1741
- }
1742
- if (oldestLid) {
1743
- const mapping = this.lidToPn.get(oldestLid)
1744
- if (mapping) {
1745
- this.pnToLid.delete(mapping.pn)
1746
- }
1747
- this.lidToPn.delete(oldestLid)
1748
- }
1749
- }
1750
- }
1751
-
1752
-
1753
- var DEFAULT_CONFIG11 = {
1754
- enabled: false,
1755
- canonicalizeOutbound: true,
1756
- learnFromEvents: true
1757
- }
1758
- var JidCanonicalizer = class {
1759
- config
1760
- lidResolver
1761
- ownsResolver
1762
-
1763
- stats = {
1764
- outboundCanonicalized: 0,
1765
- outboundPassthrough: 0,
1766
- inboundLearned: 0,
1767
- canonicalKeyHits: 0,
1768
- canonicalKeyMisses: 0
1769
- }
1770
- constructor(config = {}) {
1771
- this.config = { ...DEFAULT_CONFIG11, ...config }
1772
- if (config.resolver) {
1773
- this.lidResolver = config.resolver
1774
- this.ownsResolver = false
1775
- } else {
1776
- this.lidResolver = new LidResolver(config.resolverConfig)
1777
- this.ownsResolver = true
1778
- }
1779
- }
1780
-
1781
- get resolver() {
1782
- return this.lidResolver
1783
- }
1784
-
1785
- canonicalizeTarget(jid) {
1786
- if (!this.config.enabled || !this.config.canonicalizeOutbound) {
1787
- return jid
1788
- }
1789
- const canonical = this.lidResolver.resolveCanonical(jid)
1790
- if (canonical !== jid) {
1791
- this.stats.outboundCanonicalized++
1792
- } else {
1793
- this.stats.outboundPassthrough++
1794
- }
1795
- return canonical
1796
- }
1797
-
1798
- canonicalKey(jid) {
1799
- if (!jid || typeof jid !== 'string' || jid.trim() === '') {
1800
- return 'thread:invalid'
1801
- }
1802
- const normalized = jid.trim().toLowerCase()
1803
- const atIndex = normalized.indexOf('@')
1804
- if (atIndex === -1) {
1805
- return 'thread:invalid'
1806
- }
1807
- const user = normalized.substring(0, atIndex)
1808
- const domain = normalized.substring(atIndex + 1)
1809
- if (domain === 'g.us') {
1810
- return `thread:group:${user}`
1811
- }
1812
- if (domain === 'broadcast') {
1813
- return `thread:broadcast:${user}`
1814
- }
1815
- if (domain === 'newsletter') {
1816
- return `thread:newsletter:${user}`
1817
- }
1818
- if (domain === 's.whatsapp.net') {
1819
- this.stats.canonicalKeyHits++
1820
- return `thread:${user}`
1821
- }
1822
- if (domain === 'lid') {
1823
- const mapping = this.lidResolver.getMapping(normalized)
1824
- if (mapping?.pn) {
1825
- const pnUser = mapping.pn.split('@')[0]
1826
- this.stats.canonicalKeyHits++
1827
- return `thread:${pnUser}`
1828
- } else {
1829
- this.stats.canonicalKeyMisses++
1830
- return `thread:lid:${user}`
1831
- }
1832
- }
1833
- return `thread:${domain}:${user}`
1834
- }
1835
-
1836
- onIncomingEvent(upsert) {
1837
- if (!this.config.enabled || !this.config.learnFromEvents) {
1838
- return
1839
- }
1840
- for (const msg of upsert.messages || []) {
1841
- this.learnFromMessage(msg)
1842
- }
1843
- }
1844
-
1845
- onMessageUpdate(updates) {
1846
- if (!this.config.enabled || !this.config.learnFromEvents) {
1847
- return
1848
- }
1849
- for (const update of updates) {
1850
- if (update.key) {
1851
- this.learnFromMessageKey(update.key)
1852
- }
1853
- }
1854
- }
1855
- getStats() {
1856
- return {
1857
- resolver: this.lidResolver.getStats(),
1858
- outboundCanonicalized: this.stats.outboundCanonicalized,
1859
- outboundPassthrough: this.stats.outboundPassthrough,
1860
- inboundLearned: this.stats.inboundLearned,
1861
- canonicalKeyHits: this.stats.canonicalKeyHits,
1862
- canonicalKeyMisses: this.stats.canonicalKeyMisses
1863
- }
1864
- }
1865
- destroy() {
1866
- if (this.ownsResolver) {
1867
- this.lidResolver.destroy()
1868
- }
1869
- }
1870
-
1871
-
1872
- learnFromMessage(msg) {
1873
- if (!msg.key) return
1874
- this.learnFromMessageKey(msg.key)
1875
- if (msg.participantPn && msg.key.participant) {
1876
- this.lidResolver.learn({
1877
- lid: msg.key.participant.endsWith('@lid') ? msg.key.participant : void 0,
1878
- pn: msg.participantPn
1879
- })
1880
- this.stats.inboundLearned++
1881
- }
1882
- }
1883
-
1884
- learnFromMessageKey(key) {
1885
- if (!key) return
1886
- if (key.participant && key.participantPn) {
1887
- if (key.participant.endsWith('@lid')) {
1888
- this.lidResolver.learn({
1889
- lid: key.participant,
1890
- pn: key.participantPn
1891
- })
1892
- this.stats.inboundLearned++
1893
- }
1894
- }
1895
- if (key.remoteJid && key.senderPn) {
1896
- if (key.remoteJid.endsWith('@lid')) {
1897
- this.lidResolver.learn({
1898
- lid: key.remoteJid,
1899
- pn: key.senderPn
1900
- })
1901
- this.stats.inboundLearned++
1902
- }
1903
- }
1904
- if (key.participant && key.remoteJid) {
1905
- if (key.participant.endsWith('@s.whatsapp.net') && key.remoteJid.endsWith('@lid')) {
1906
- this.lidResolver.learn({
1907
- lid: key.remoteJid,
1908
- pn: key.participant
1909
- })
1910
- this.stats.inboundLearned++
1911
- }
1912
- }
1913
- }
1914
- }
1915
-
1916
-
1917
- function classifyDisconnect(statusCode) {
1918
- if (statusCode === 401 || statusCode === 440) {
1919
- return {
1920
- category: 'fatal',
1921
- shouldReconnect: false,
1922
- message: 'Logged out \u2014 restart with QR code required',
1923
- code: statusCode
1924
- }
1925
- }
1926
- if (statusCode === 515) {
1927
- return {
1928
- category: 'fatal',
1929
- shouldReconnect: false,
1930
- message: 'Restart required by WhatsApp \u2014 client too old or protocol mismatch',
1931
- code: statusCode
1932
- }
1933
- }
1934
- if (statusCode === 405) {
1935
- return {
1936
- category: 'fatal',
1937
- shouldReconnect: false,
1938
- message: 'Method not allowed \u2014 server rejected connection method',
1939
- code: statusCode
1940
- }
1941
- }
1942
- if (statusCode === 409 || statusCode === 428) {
1943
- return {
1944
- category: 'fatal',
1945
- shouldReconnect: false,
1946
- message: 'Connection replaced \u2014 another device took over',
1947
- code: statusCode
1948
- }
1949
- }
1950
- if (statusCode === 412) {
1951
- return {
1952
- category: 'recoverable',
1953
- shouldReconnect: true,
1954
- backoffMs: 3e4,
1955
-
1956
- message: 'Precondition failed \u2014 auth state mismatch, retry after delay',
1957
- code: statusCode
1958
- }
1959
- }
1960
- if (statusCode === 429) {
1961
- return {
1962
- category: 'rate-limited',
1963
- shouldReconnect: true,
1964
- backoffMs: 3e5,
1965
-
1966
- message: 'Rate limited by WhatsApp \u2014 cool-off period required',
1967
- code: statusCode
1968
- }
1969
- }
1970
- if (statusCode === 503) {
1971
- return {
1972
- category: 'rate-limited',
1973
- shouldReconnect: true,
1974
- backoffMs: 6e4,
1975
-
1976
- message: 'WhatsApp service unavailable \u2014 temporary outage',
1977
- code: statusCode
1978
- }
1979
- }
1980
- if (statusCode === 408) {
1981
- return {
1982
- category: 'recoverable',
1983
- shouldReconnect: true,
1984
- backoffMs: 5e3,
1985
-
1986
- message: 'Connection timeout \u2014 network issue, safe to retry',
1987
- code: statusCode
1988
- }
1989
- }
1990
- if (statusCode === 500) {
1991
- return {
1992
- category: 'recoverable',
1993
- shouldReconnect: true,
1994
- backoffMs: 1e4,
1995
-
1996
- message: 'WhatsApp internal error \u2014 temporary server issue',
1997
- code: statusCode
1998
- }
1999
- }
2000
- if (statusCode === 1e3) {
2001
- return {
2002
- category: 'recoverable',
2003
- shouldReconnect: true,
2004
- backoffMs: 2e3,
2005
-
2006
- message: 'Connection closed gracefully \u2014 safe to reconnect',
2007
- code: statusCode
2008
- }
2009
- }
2010
- return {
2011
- category: 'unknown',
2012
- shouldReconnect: true,
2013
- backoffMs: 15e3,
2014
-
2015
- message: `Unknown disconnect reason (code ${statusCode}) \u2014 reconnect with caution`,
2016
- code: statusCode
2017
- }
2018
- }
2019
- var DEFAULT_HEALTH_CONFIG = {
2020
- badMacThreshold: 3,
2021
- badMacWindowMs: 6e4
2022
- }
2023
- var SessionHealthMonitor = class {
2024
- config
2025
- onDegraded
2026
- onRecovered
2027
- stats = {
2028
- decryptSuccess: 0,
2029
- decryptFail: 0,
2030
- badMacCount: 0,
2031
- isDegraded: false
2032
- }
2033
- badMacTimestamps = []
2034
- constructor(config = {}) {
2035
- this.config = { ...DEFAULT_HEALTH_CONFIG, ...config }
2036
- this.onDegraded = config.onDegraded
2037
- this.onRecovered = config.onRecovered
2038
- }
2039
-
2040
- recordDecryptSuccess() {
2041
- this.stats.decryptSuccess++
2042
- this.checkRecovery()
2043
- }
2044
-
2045
- recordDecryptFail(isBadMac = false) {
2046
- this.stats.decryptFail++
2047
- if (isBadMac) {
2048
- const now = Date.now()
2049
- this.stats.badMacCount++
2050
- this.stats.lastBadMac = new Date(now)
2051
- this.badMacTimestamps.push(now)
2052
- const cutoff = now - this.config.badMacWindowMs
2053
- this.badMacTimestamps = this.badMacTimestamps.filter(ts => ts > cutoff)
2054
- if (!this.stats.isDegraded && this.badMacTimestamps.length >= this.config.badMacThreshold) {
2055
- this.stats.isDegraded = true
2056
- this.stats.degradedSince = new Date(now)
2057
- this.onDegraded?.(this.getStats())
2058
- }
2059
- }
2060
- }
2061
-
2062
- checkRecovery() {
2063
- if (!this.stats.isDegraded) return
2064
- const now = Date.now()
2065
- const cutoff = now - this.config.badMacWindowMs
2066
- this.badMacTimestamps = this.badMacTimestamps.filter(ts => ts > cutoff)
2067
- if (this.badMacTimestamps.length < this.config.badMacThreshold) {
2068
- this.stats.isDegraded = false
2069
- this.stats.degradedSince = void 0
2070
- this.onRecovered?.(this.getStats())
2071
- }
2072
- }
2073
-
2074
- getStats() {
2075
- return { ...this.stats }
2076
- }
2077
-
2078
- reset() {
2079
- this.stats = {
2080
- decryptSuccess: 0,
2081
- decryptFail: 0,
2082
- badMacCount: 0,
2083
- isDegraded: false
2084
- }
2085
- this.badMacTimestamps = []
2086
- }
2087
- }
2088
- function wrapWithSessionStability(sock, config = {}) {
2089
- const { canonicalJidNormalization = true, healthMonitoring = true, health: healthConfig, lidResolver } = config
2090
- const healthMonitor = healthMonitoring ? new SessionHealthMonitor(healthConfig) : null
2091
- return new Proxy(sock, {
2092
- get(target, prop) {
2093
- if (prop === 'sendMessage' && canonicalJidNormalization && lidResolver) {
2094
- return async (jid, content, options) => {
2095
- const canonical = lidResolver.resolveCanonical(jid)
2096
- return target.sendMessage(canonical, content, options)
2097
- }
2098
- }
2099
- if (prop === 'sessionHealthStats' && healthMonitor) {
2100
- return healthMonitor.getStats()
2101
- }
2102
- if (prop === 'sessionHealthMonitor' && healthMonitor) {
2103
- return healthMonitor
2104
- }
2105
- return target[prop]
2106
- }
2107
- })
2108
- }
2109
-
2110
-
2111
- var PRESETS = {
2112
- conservative: {
2113
- maxPerMinute: 6,
2114
- maxPerHour: 150,
2115
- maxPerDay: 1000,
2116
- minDelayMs: 2000,
2117
- maxDelayMs: 6e3,
2118
- newChatDelayMs: 3e3,
2119
- warmupDays: 7,
2120
- day1Limit: 20,
2121
- growthFactor: 1.8,
2122
- inactivityThresholdHours: 120,
2123
- autoPauseAt: 'high',
2124
- groupMultiplier: 0.5,
2125
- groupProfiles: false,
2126
- logging: true
2127
- },
2128
- moderate: {
2129
- maxPerMinute: 15,
2130
- maxPerHour: 500,
2131
- maxPerDay: 3e3,
2132
- minDelayMs: 1e3,
2133
- maxDelayMs: 4e3,
2134
- newChatDelayMs: 2e3,
2135
- warmupDays: 5,
2136
- day1Limit: 30,
2137
- growthFactor: 1.8,
2138
- inactivityThresholdHours: 168,
2139
- autoPauseAt: 'critical',
2140
- groupMultiplier: 0.7,
2141
- groupProfiles: false,
2142
- logging: true
2143
- },
2144
- aggressive: {
2145
- maxPerMinute: 25,
2146
- maxPerHour: 1200,
2147
- maxPerDay: 6e3,
2148
- minDelayMs: 600,
2149
- maxDelayMs: 2500,
2150
- newChatDelayMs: 1500,
2151
- warmupDays: 3,
2152
- day1Limit: 50,
2153
- growthFactor: 2,
2154
- inactivityThresholdHours: 96,
2155
- autoPauseAt: 'critical',
2156
- groupMultiplier: 0.9,
2157
- groupProfiles: false,
2158
- logging: true
2159
- }
2160
- }
2161
- function resolveConfig(input) {
2162
- if (input === void 0) {
2163
- return { ...PRESETS.moderate }
2164
- }
2165
- if (typeof input === 'string') {
2166
- if (!(input in PRESETS)) {
2167
- throw new Error(`Unknown preset "${input}". Valid: ${Object.keys(PRESETS).join(', ')}`)
2168
- }
2169
- return { ...PRESETS[input] }
2170
- }
2171
- const { preset = 'moderate', ...overrides } = input
2172
- if (!(preset in PRESETS)) {
2173
- throw new Error(`Unknown preset "${preset}". Valid: ${Object.keys(PRESETS).join(', ')}`)
2174
- }
2175
- return { ...PRESETS[preset], ...overrides }
2176
- }
2177
-
2178
-
2179
- var fs = __toESM(require('fs'), 1)
2180
- var KNOWN_CHATS_MAX = 1e3
2181
- var DEBOUNCE_MS = 5e3
2182
- var StateManager = class {
2183
- path
2184
- debounceTimer = null
2185
- constructor(filePath) {
2186
- this.path = filePath
2187
- }
2188
- load() {
2189
- try {
2190
- const raw = fs.readFileSync(this.path, 'utf-8')
2191
- const parsed = JSON.parse(raw)
2192
- if (parsed.version !== 3) {
2193
- process.stderr.write('[baileys-antiban] WARN: corrupt state file or version mismatch, starting fresh\n')
2194
- return null
2195
- }
2196
- return parsed
2197
- } catch {
2198
- if (fs.existsSync(this.path)) {
2199
- process.stderr.write('[baileys-antiban] WARN: corrupt state file, starting fresh\n')
2200
- }
2201
- return null
2202
- }
2203
- }
2204
-
2205
- saveDebounced(state) {
2206
- if (this.debounceTimer) {
2207
- clearTimeout(this.debounceTimer)
2208
- }
2209
- this.debounceTimer = setTimeout(() => {
2210
- this.writeFile(state)
2211
- this.debounceTimer = null
2212
- }, DEBOUNCE_MS)
2213
- }
2214
-
2215
- saveImmediate(state) {
2216
- if (this.debounceTimer) {
2217
- clearTimeout(this.debounceTimer)
2218
- this.debounceTimer = null
2219
- }
2220
- this.writeFile(state)
2221
- }
2222
-
2223
- flush() {
2224
- if (this.debounceTimer) {
2225
- clearTimeout(this.debounceTimer)
2226
- this.debounceTimer = null
2227
- }
2228
- }
2229
- destroy() {
2230
- this.flush()
2231
- }
2232
- writeFile(state) {
2233
- const toSave = {
2234
- ...state,
2235
- savedAt: Date.now(),
2236
-
2237
- knownChats:
2238
- state.knownChats.length > KNOWN_CHATS_MAX ? state.knownChats.slice(-KNOWN_CHATS_MAX) : state.knownChats
2239
- }
2240
- try {
2241
- fs.writeFileSync(this.path, JSON.stringify(toSave, null, 2), 'utf-8')
2242
- } catch (err) {
2243
- process.stderr.write(`[baileys-antiban] WARN: failed to write state to ${this.path}: ${err}\n`)
2244
- }
2245
- }
2246
- }
2247
-
2248
-
2249
- function isGroup(jid) {
2250
- return jid.endsWith('@g.us')
2251
- }
2252
- function isNewsletter(jid) {
2253
- return jid.endsWith('@newsletter')
2254
- }
2255
- function isBroadcast(jid) {
2256
- return jid === 'status@broadcast' || jid.endsWith('@broadcast')
2257
- }
2258
- function shouldUseGroupProfile(jid) {
2259
- return isGroup(jid) || isNewsletter(jid)
2260
- }
2261
- function applyGroupMultiplier(limits, multiplier) {
2262
- return {
2263
- maxPerMinute: Math.max(1, Math.floor(limits.maxPerMinute * multiplier)),
2264
- maxPerHour: Math.max(1, Math.floor(limits.maxPerHour * multiplier)),
2265
- maxPerDay: Math.max(1, Math.floor(limits.maxPerDay * multiplier))
2266
- }
2267
- }
2268
-
2269
-
2270
-
2271
-
2272
-
2273
-
2274
-
2275
-
2276
-
2277
- const CORE_LEGACY_KEYS = ['rateLimiter', 'warmUp']
2278
- function hasLegacyCoreNesting(cfg) {
2279
- if (typeof cfg !== 'object' || cfg === null) return false
2280
- return CORE_LEGACY_KEYS.some(key => key in cfg)
2281
- }
2282
- function mapLegacyToFlat(legacy) {
2283
- process.stderr.write(
2284
- '[baileys-antiban] DEPRECATED: nested { rateLimiter, warmUp } config detected. Migrate to flat fields: new AntiBan({ maxPerMinute: 8, warmupDays: 5 }). Flat top-level fields you also pass always take precedence.\n'
2285
- )
2286
- const flat = {}
2287
- if (legacy.rateLimiter?.maxPerMinute !== void 0) flat.maxPerMinute = legacy.rateLimiter.maxPerMinute
2288
- if (legacy.rateLimiter?.maxPerHour !== void 0) flat.maxPerHour = legacy.rateLimiter.maxPerHour
2289
- if (legacy.rateLimiter?.maxPerDay !== void 0) flat.maxPerDay = legacy.rateLimiter.maxPerDay
2290
- if (legacy.rateLimiter?.minDelayMs !== void 0) flat.minDelayMs = legacy.rateLimiter.minDelayMs
2291
- if (legacy.rateLimiter?.maxDelayMs !== void 0) flat.maxDelayMs = legacy.rateLimiter.maxDelayMs
2292
- if (legacy.rateLimiter?.newChatDelayMs !== void 0) flat.newChatDelayMs = legacy.rateLimiter.newChatDelayMs
2293
- if (legacy.warmUp?.warmUpDays !== void 0) flat.warmupDays = legacy.warmUp.warmUpDays
2294
- if (legacy.warmUp?.day1Limit !== void 0) flat.day1Limit = legacy.warmUp.day1Limit
2295
- if (legacy.warmUp?.growthFactor !== void 0) flat.growthFactor = legacy.warmUp.growthFactor
2296
- return flat
2297
- }
2298
- var AntiBan = class {
2299
- rateLimiter
2300
- warmUp
2301
- health
2302
- timelockGuard
2303
- replyRatioGuard
2304
- contactGraphWarmer
2305
- presenceChoreographer
2306
- retryTrackerModule
2307
- reconnectThrottleModule
2308
- lidResolverModule = null
2309
- jidCanonicalizerModule = null
2310
- sessionStabilityMonitor = null
2311
- stateManager = null
2312
- resolvedConfig
2313
- logging
2314
- stats = {
2315
- messagesAllowed: 0,
2316
- messagesBlocked: 0,
2317
- totalDelayMs: 0
2318
- }
2319
- constructor(input, warmUpStateArg) {
2320
- let warmUpState = warmUpStateArg
2321
-
2322
-
2323
-
2324
-
2325
- const rawInput = typeof input === 'object' && input !== null ? input : {}
2326
- const legacyNested = hasLegacyCoreNesting(rawInput)
2327
-
2328
- const flatMerged = legacyNested ? { ...mapLegacyToFlat(rawInput), ...rawInput } : rawInput
2329
- const cfg = typeof input === 'string' ? resolveConfig(input) : resolveConfig(flatMerged)
2330
- this.resolvedConfig = cfg
2331
- const rateLimiterOverrides = rawInput.rateLimiter || {}
2332
- const warmUpOverrides = rawInput.warmUp || {}
2333
- const healthOverrides = rawInput.health || {}
2334
- const timelockOverrides = rawInput.timelock || {}
2335
- const replyRatioCfg = rawInput.replyRatio
2336
- const contactGraphCfg = rawInput.contactGraph
2337
- const presenceCfg = rawInput.presence
2338
- const retryTrackerCfg = rawInput.retryTracker
2339
- const reconnectThrottleCfg = rawInput.reconnectThrottle
2340
- const lidResolverCfg = rawInput.lidResolver
2341
- const jidCanonicalizerCfg = rawInput.jidCanonicalizer
2342
- const sessionStabilityCfg = rawInput.sessionStability
2343
- let savedState = null
2344
- if (cfg.persist) {
2345
- this.stateManager = new StateManager(cfg.persist)
2346
- savedState = this.stateManager.load()
2347
- if (savedState) {
2348
- warmUpState = savedState.warmup
2349
- }
2350
- }
2351
- this.logging = cfg.logging ?? true
2352
- this._log = msg => {
2353
- if (this.logging) process.stdout.write(`[baileys-antiban] ${msg}\n`)
2354
- }
2355
- this.rateLimiter = new RateLimiter({
2356
- maxPerMinute: cfg.maxPerMinute,
2357
- maxPerHour: cfg.maxPerHour,
2358
- maxPerDay: cfg.maxPerDay,
2359
- minDelayMs: cfg.minDelayMs,
2360
- maxDelayMs: cfg.maxDelayMs,
2361
- newChatDelayMs: cfg.newChatDelayMs,
2362
- ...rateLimiterOverrides
2363
- })
2364
- if (savedState?.knownChats) {
2365
- this.rateLimiter.restoreKnownChats(savedState.knownChats)
2366
- }
2367
- this.warmUp = new WarmUp(
2368
- {
2369
- warmUpDays: cfg.warmupDays,
2370
- day1Limit: cfg.day1Limit,
2371
- growthFactor: cfg.growthFactor,
2372
- inactivityThresholdHours: cfg.inactivityThresholdHours,
2373
- ...warmUpOverrides
2374
- },
2375
- warmUpState
2376
- )
2377
- this.health = new HealthMonitor({
2378
- autoPauseAt: cfg.autoPauseAt,
2379
- ...healthOverrides,
2380
- onRiskChange: status => {
2381
- const emoji = { low: '\u{1F7E2}', medium: '\u{1F7E1}', high: '\u{1F7E0}', critical: '\u{1F534}' }
2382
- this._log(`${emoji[status.risk]} Risk level: ${status.risk.toUpperCase()} (score: ${status.score})`)
2383
- this._log(status.recommendation)
2384
- status.reasons.forEach(r => this._log(` \u2192 ${r}`))
2385
- healthOverrides.onRiskChange?.(status)
2386
- }
2387
- })
2388
- this.timelockGuard = new TimelockGuard({
2389
- ...timelockOverrides,
2390
- onTimelockDetected: state => {
2391
- this.health.recordReachoutTimelock(state.enforcementType)
2392
- this._log(
2393
- `REACHOUT TIMELOCKED \u2014 ${state.enforcementType || 'unknown'}, expires ${state.expiresAt?.toISOString() || 'unknown'}`
2394
- )
2395
- timelockOverrides.onTimelockDetected?.(state)
2396
- },
2397
- onTimelockLifted: state => {
2398
- this._log('Timelock lifted \u2014 resuming new contact messages')
2399
- timelockOverrides.onTimelockLifted?.(state)
2400
- }
2401
- })
2402
- this.replyRatioGuard = new ReplyRatioGuard(replyRatioCfg)
2403
- this.contactGraphWarmer = new ContactGraphWarmer(contactGraphCfg)
2404
- this.presenceChoreographer = new PresenceChoreographer(presenceCfg)
2405
- this.retryTrackerModule = new RetryReasonTracker({
2406
- ...(retryTrackerCfg || {}),
2407
- onSpiral: (msgId, reason) => {
2408
- this._log(`\u26A0\uFE0F Message ${msgId} stuck in retry spiral (${reason})`)
2409
- retryTrackerCfg?.onSpiral?.(msgId, reason)
2410
- }
2411
- })
2412
- this.reconnectThrottleModule = new PostReconnectThrottle({
2413
- ...(reconnectThrottleCfg || {}),
2414
- baselineRatePerMinute: () => this.rateLimiter.getStats().limits.perMinute
2415
- })
2416
- if (jidCanonicalizerCfg?.enabled) {
2417
- if (jidCanonicalizerCfg.resolver) {
2418
- this.jidCanonicalizerModule = new JidCanonicalizer(jidCanonicalizerCfg)
2419
- this.lidResolverModule = jidCanonicalizerCfg.resolver
2420
- } else {
2421
- const resolverConfig = lidResolverCfg || jidCanonicalizerCfg.resolverConfig
2422
- const resolver = new LidResolver(resolverConfig)
2423
- this.lidResolverModule = resolver
2424
- this.jidCanonicalizerModule = new JidCanonicalizer({
2425
- ...jidCanonicalizerCfg,
2426
- resolver
2427
- })
2428
- }
2429
- } else if (lidResolverCfg) {
2430
- this.lidResolverModule = new LidResolver(lidResolverCfg)
2431
- }
2432
- if (sessionStabilityCfg?.enabled) {
2433
- const healthConfig = {
2434
- badMacThreshold: sessionStabilityCfg.badMacThreshold,
2435
- badMacWindowMs: sessionStabilityCfg.badMacWindowMs,
2436
- onDegraded: stats => {
2437
- this._log(
2438
- `\u{1F534} SESSION DEGRADED \u2014 Bad MAC rate: ${stats.badMacCount} in last ${sessionStabilityCfg.badMacWindowMs || 6e4}ms`
2439
- )
2440
- this._log('Consider restarting session or switching to LID-based canonical form')
2441
- },
2442
- onRecovered: () => {
2443
- this._log('\u{1F7E2} SESSION RECOVERED \u2014 decrypt success rate improved')
2444
- }
2445
- }
2446
- this.sessionStabilityMonitor = new SessionHealthMonitor(healthConfig)
2447
- }
2448
- }
2449
-
2450
- async beforeSend(recipient, content, dedupKey) {
2451
- const healthStatus = this.health.getStatus()
2452
- if (this.health.isPaused()) {
2453
- this.stats.messagesBlocked++
2454
- this._log(`\u26D4 BLOCKED \u2014 health risk too high (${healthStatus.risk})`)
2455
- return {
2456
- allowed: false,
2457
- delayMs: 0,
2458
- reason: `Health risk ${healthStatus.risk}: ${healthStatus.recommendation}`,
2459
- health: healthStatus
2460
- }
2461
- }
2462
- const timelockDecision = this.timelockGuard.canSend(recipient)
2463
- if (!timelockDecision.allowed) {
2464
- this.stats.messagesBlocked++
2465
- this._log(`TIMELOCKED \u2014 ${timelockDecision.reason}`)
2466
- return {
2467
- allowed: false,
2468
- delayMs: 0,
2469
- reason: timelockDecision.reason,
2470
- health: healthStatus
2471
- }
2472
- }
2473
- if (!this.warmUp.canSend()) {
2474
- this.stats.messagesBlocked++
2475
- const warmUpStatus = this.warmUp.getStatus()
2476
- this._log(
2477
- `\u23F3 BLOCKED \u2014 warm-up day ${warmUpStatus.day}/${warmUpStatus.totalDays}, limit reached (${warmUpStatus.todaySent}/${warmUpStatus.todayLimit})`
2478
- )
2479
- return {
2480
- allowed: false,
2481
- delayMs: 0,
2482
- reason: `Warm-up limit: ${warmUpStatus.todaySent}/${warmUpStatus.todayLimit} messages today (day ${warmUpStatus.day})`,
2483
- health: healthStatus,
2484
- warmUpDay: warmUpStatus.day
2485
- }
2486
- }
2487
- const contactGraphDecision = this.contactGraphWarmer.canMessage(recipient)
2488
- if (!contactGraphDecision.allowed) {
2489
- this.stats.messagesBlocked++
2490
- this._log(`\u{1F4CA} BLOCKED \u2014 contact graph: ${contactGraphDecision.reason}`)
2491
- return {
2492
- allowed: false,
2493
- delayMs: 0,
2494
- reason: `Contact graph: ${contactGraphDecision.reason}`,
2495
- health: healthStatus
2496
- }
2497
- }
2498
- const replyRatioDecision = this.replyRatioGuard.beforeSend(recipient)
2499
- if (!replyRatioDecision.allowed) {
2500
- this.stats.messagesBlocked++
2501
- this._log(`\u{1F4AC} BLOCKED \u2014 reply ratio: ${replyRatioDecision.reason}`)
2502
- return {
2503
- allowed: false,
2504
- delayMs: 0,
2505
- reason: `Reply ratio: ${replyRatioDecision.reason}`,
2506
- health: healthStatus
2507
- }
2508
- }
2509
- const reconnectThrottleDecision = this.reconnectThrottleModule.beforeSend()
2510
- if (!reconnectThrottleDecision.allowed) {
2511
- this.stats.messagesBlocked++
2512
- this._log(`\u{1F504} BLOCKED \u2014 reconnect throttle: ${reconnectThrottleDecision.reason}`)
2513
- return {
2514
- allowed: false,
2515
- delayMs: reconnectThrottleDecision.retryAfterMs || 0,
2516
- reason: reconnectThrottleDecision.reason || 'Post-reconnect throttle',
2517
- health: healthStatus
2518
- }
2519
- }
2520
- if (this.resolvedConfig.groupProfiles && shouldUseGroupProfile(recipient)) {
2521
- const groupLimits = applyGroupMultiplier(
2522
- {
2523
- maxPerMinute: this.resolvedConfig.maxPerMinute,
2524
- maxPerHour: this.resolvedConfig.maxPerHour,
2525
- maxPerDay: this.resolvedConfig.maxPerDay
2526
- },
2527
- this.resolvedConfig.groupMultiplier
2528
- )
2529
- const stats = this.rateLimiter.getStats()
2530
- if (
2531
- stats.lastMinute >= groupLimits.maxPerMinute ||
2532
- stats.lastHour >= groupLimits.maxPerHour ||
2533
- stats.lastDay >= groupLimits.maxPerDay
2534
- ) {
2535
- this.stats.messagesBlocked++
2536
- this._log(`\u{1F6AB} BLOCKED \u2014 group rate limit exceeded for ${recipient}`)
2537
- return { allowed: false, delayMs: 0, reason: 'Group rate limit exceeded', health: healthStatus }
2538
- }
2539
- }
2540
- let delay = await this.rateLimiter.getDelay(recipient, content, dedupKey)
2541
- if (delay === -1) {
2542
- this.stats.messagesBlocked++
2543
- this._log(`\u{1F6AB} BLOCKED \u2014 rate limit or identical message spam`)
2544
- return {
2545
- allowed: false,
2546
- delayMs: 0,
2547
- reason: 'Rate limit exceeded or identical message spam detected',
2548
- health: healthStatus
2549
- }
2550
- }
2551
- const activityFactor = this.presenceChoreographer.getCurrentActivityFactor()
2552
- if (activityFactor < 1) {
2553
- const multiplier = Math.min(5, 1 / activityFactor)
2554
- delay = Math.floor(delay * multiplier)
2555
- }
2556
- const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction()
2557
- if (distractionCheck.pause) {
2558
- delay += distractionCheck.durationMs
2559
- this._log(`\u23F8\uFE0F Distraction pause: +${Math.floor(distractionCheck.durationMs / 6e4)}min`)
2560
- }
2561
- const offlineCheck = this.presenceChoreographer.shouldTakeOfflineGap()
2562
- if (offlineCheck.offline) {
2563
- delay += offlineCheck.durationMs
2564
- this._log(`\u{1F4F4} Offline gap: +${Math.floor(offlineCheck.durationMs / 6e4)}min`)
2565
- }
2566
- this.stats.totalDelayMs += delay
2567
- return {
2568
- allowed: true,
2569
- delayMs: delay,
2570
- health: healthStatus
2571
- }
2572
- }
2573
-
2574
- afterSend(recipient, content, dedupKey) {
2575
- this.rateLimiter.record(recipient, content, dedupKey)
2576
- this.warmUp.record()
2577
- this.replyRatioGuard.recordSent(recipient)
2578
- this.stats.messagesAllowed++
2579
- this.persistStateDebounced()
2580
- }
2581
-
2582
- afterSendFailed(error) {
2583
- this.health.recordMessageFailed(error)
2584
- }
2585
-
2586
- onDisconnect(reason) {
2587
- this.health.recordDisconnect(reason)
2588
- this.reconnectThrottleModule.onDisconnect()
2589
- const reasonStr = String(reason)
2590
- if (reasonStr === '403' || reasonStr === '401' || reasonStr === 'forbidden' || reasonStr === 'loggedOut') {
2591
- this.persistStateImmediate()
2592
- }
2593
- }
2594
-
2595
- onReconnect() {
2596
- this.health.recordReconnect()
2597
- this.reconnectThrottleModule.onReconnect()
2598
- }
2599
-
2600
- onIncomingMessage(jid, msgText) {
2601
- this.replyRatioGuard.recordReceived(jid)
2602
- this.contactGraphWarmer.onIncomingMessage(jid)
2603
- return this.replyRatioGuard.suggestReply(jid, msgText)
2604
- }
2605
-
2606
- getStats() {
2607
- const stats = {
2608
- ...this.stats,
2609
- health: this.health.getStatus(),
2610
- warmUp: this.warmUp.getStatus(),
2611
- rateLimiter: this.rateLimiter.getStats()
2612
- }
2613
- if (this.replyRatioGuard['config']?.enabled) {
2614
- stats.replyRatio = this.replyRatioGuard.getStats()
2615
- }
2616
- if (this.contactGraphWarmer['config']?.enabled) {
2617
- stats.contactGraph = this.contactGraphWarmer.getStats()
2618
- }
2619
- if (this.presenceChoreographer['config']?.enabled) {
2620
- stats.presence = this.presenceChoreographer.getStats()
2621
- }
2622
- if (this.retryTrackerModule['config']?.enabled) {
2623
- stats.retryTracker = this.retryTrackerModule.getStats()
2624
- }
2625
- if (this.reconnectThrottleModule['config']?.enabled) {
2626
- stats.reconnectThrottle = this.reconnectThrottleModule.getStats()
2627
- }
2628
- if (this.lidResolverModule) {
2629
- stats.lidResolver = this.lidResolverModule.getStats()
2630
- }
2631
- if (this.jidCanonicalizerModule) {
2632
- stats.jidCanonicalizer = this.jidCanonicalizerModule.getStats()
2633
- }
2634
- if (this.sessionStabilityMonitor) {
2635
- stats.sessionStability = this.sessionStabilityMonitor.getStats()
2636
- }
2637
- return stats
2638
- }
2639
-
2640
- get timelock() {
2641
- return this.timelockGuard
2642
- }
2643
-
2644
- get replyRatio() {
2645
- return this.replyRatioGuard
2646
- }
2647
-
2648
- get contactGraph() {
2649
- return this.contactGraphWarmer
2650
- }
2651
-
2652
- get presence() {
2653
- return this.presenceChoreographer
2654
- }
2655
-
2656
- get retryTracker() {
2657
- return this.retryTrackerModule
2658
- }
2659
-
2660
- get reconnectThrottle() {
2661
- return this.reconnectThrottleModule
2662
- }
2663
-
2664
- get lidResolver() {
2665
- return this.lidResolverModule
2666
- }
2667
-
2668
- get jidCanonicalizer() {
2669
- return this.jidCanonicalizerModule
2670
- }
2671
-
2672
- get sessionStability() {
2673
- return this.sessionStabilityMonitor
2674
- }
2675
-
2676
- exportWarmUpState() {
2677
- return this.warmUp.exportState()
2678
- }
2679
-
2680
- pause() {
2681
- this.health.setPaused(true)
2682
- this._log('\u23F8\uFE0F Sending paused manually')
2683
- }
2684
-
2685
- resume() {
2686
- this.health.setPaused(false)
2687
- this._log('\u25B6\uFE0F Sending resumed')
2688
- }
2689
-
2690
- reset() {
2691
- this.timelockGuard.reset()
2692
- this.health.reset()
2693
- this.warmUp.reset()
2694
- this.replyRatioGuard.reset()
2695
- this.contactGraphWarmer.reset()
2696
- this.presenceChoreographer.reset()
2697
- this.retryTrackerModule.destroy()
2698
- this.reconnectThrottleModule.destroy()
2699
- this.stats = { messagesAllowed: 0, messagesBlocked: 0, totalDelayMs: 0 }
2700
- this._log('\u{1F504} Reset \u2014 starting fresh warm-up')
2701
- }
2702
- persistStateDebounced() {
2703
- if (!this.stateManager) return
2704
- const state = {
2705
- warmup: this.warmUp.exportState(),
2706
- knownChats: Array.from(this.rateLimiter.getKnownChats()),
2707
- savedAt: Date.now(),
2708
- version: 3
2709
- }
2710
- this.stateManager.saveDebounced(state)
2711
- }
2712
- persistStateImmediate() {
2713
- if (!this.stateManager) return
2714
- const state = {
2715
- warmup: this.warmUp.exportState(),
2716
- knownChats: Array.from(this.rateLimiter.getKnownChats()),
2717
- savedAt: Date.now(),
2718
- version: 3
2719
- }
2720
- this.stateManager.saveImmediate(state)
2721
- }
2722
-
2723
- destroy() {
2724
- this.stateManager?.destroy()
2725
- this.timelockGuard.reset()
2726
- this.replyRatioGuard.reset()
2727
- this.contactGraphWarmer.reset()
2728
- this.presenceChoreographer.reset()
2729
- this.retryTrackerModule.destroy()
2730
- this.reconnectThrottleModule.destroy()
2731
- this.jidCanonicalizerModule?.destroy()
2732
- this.lidResolverModule?.destroy()
2733
- this.sessionStabilityMonitor?.reset()
2734
- this._log('\u{1F9F9} Destroyed \u2014 all timers cleared')
2735
- }
2736
- }
2737
-
2738
-
2739
- var fs2 = __toESM(require('fs'), 1)
2740
- var path = __toESM(require('path'), 1)
2741
- var LidFirstResolver = class {
2742
- lidToPhone = new Map()
2743
- phoneToLid = new Map()
2744
-
2745
-
2746
- loadFromAuthDir(authDir) {
2747
- try {
2748
- if (!fs2.existsSync(authDir)) {
2749
- return
2750
- }
2751
- const files = fs2.readdirSync(authDir)
2752
- const reverseMappingFiles = files.filter(f => f.startsWith('lid-mapping-') && f.endsWith('_reverse.json'))
2753
- for (const file of reverseMappingFiles) {
2754
- const filePath = path.join(authDir, file)
2755
- const content = fs2.readFileSync(filePath, 'utf-8')
2756
- const data = JSON.parse(content)
2757
- for (const [lid, pnJid] of Object.entries(data)) {
2758
- if (typeof pnJid === 'string') {
2759
- const phone = this.extractPhone(pnJid)
2760
- if (phone && lid.endsWith('@lid')) {
2761
- const mapping = {
2762
- lid: this.normalizeLid(lid),
2763
- phone,
2764
- learnedAt: Date.now(),
2765
- source: 'auth-dir'
2766
- }
2767
- this.lidToPhone.set(mapping.lid, mapping)
2768
- this.phoneToLid.set(phone, mapping.lid)
2769
- }
2770
- }
2771
- }
2772
- }
2773
- } catch (error) {}
2774
- }
2775
-
2776
- learnFromEvent(event) {
2777
- try {
2778
- if (event.key?.remoteJid) {
2779
- const jid = event.key.remoteJid
2780
- this.learnJid(jid, 'event')
2781
- }
2782
- if (event.key?.participant) {
2783
- const jid = event.key.participant
2784
- this.learnJid(jid, 'event')
2785
- }
2786
- if (event.id) {
2787
- this.learnJid(event.id, 'event')
2788
- }
2789
- if (event.pushName && event.key?.remoteJid) {
2790
- this.learnJid(event.key.remoteJid, 'event')
2791
- }
2792
- } catch (error) {}
2793
- }
2794
-
2795
- resolveToLID(phoneOrJid) {
2796
- const phone = this.extractPhone(phoneOrJid)
2797
- if (!phone) return null
2798
- return this.phoneToLid.get(phone) || null
2799
- }
2800
-
2801
- resolveToPhone(lid) {
2802
- const normalized = this.normalizeLid(lid)
2803
- const mapping = this.lidToPhone.get(normalized)
2804
- return mapping ? mapping.phone : null
2805
- }
2806
-
2807
- getMapping(jid) {
2808
- const normalized = this.normalizeLid(jid)
2809
- const byLid = this.lidToPhone.get(normalized)
2810
- if (byLid) return byLid
2811
- const phone = this.extractPhone(jid)
2812
- if (phone) {
2813
- const lid = this.phoneToLid.get(phone)
2814
- if (lid) return this.lidToPhone.get(lid) || null
2815
- }
2816
- return null
2817
- }
2818
-
2819
- size() {
2820
- return this.lidToPhone.size
2821
- }
2822
-
2823
- clear() {
2824
- this.lidToPhone.clear()
2825
- this.phoneToLid.clear()
2826
- }
2827
-
2828
- learnJid(_jid, _source) {}
2829
- extractPhone(jid) {
2830
- if (!jid) return null
2831
- let cleaned = jid.replace('@s.whatsapp.net', '')
2832
- cleaned = cleaned.replace(/:\d+$/, '')
2833
- if (/^\d+$/.test(cleaned)) {
2834
- return cleaned
2835
- }
2836
- return null
2837
- }
2838
- normalizeLid(lid) {
2839
- return lid.replace(/:\d+@/, '@')
2840
- }
2841
- }
2842
- function createLidFirstResolver() {
2843
- return new LidFirstResolver()
2844
- }
2845
-
2846
-
2847
- var MessageRetryReason
2848
- ;(function (MessageRetryReason2) {
2849
- MessageRetryReason2[(MessageRetryReason2['UnknownError'] = 0)] = 'UnknownError'
2850
- MessageRetryReason2[(MessageRetryReason2['GenericError'] = 1)] = 'GenericError'
2851
- MessageRetryReason2[(MessageRetryReason2['SignalErrorInvalidKeyId'] = 3)] = 'SignalErrorInvalidKeyId'
2852
- MessageRetryReason2[(MessageRetryReason2['SignalErrorInvalidMessage'] = 4)] = 'SignalErrorInvalidMessage'
2853
- MessageRetryReason2[(MessageRetryReason2['SignalErrorNoSession'] = 5)] = 'SignalErrorNoSession'
2854
- MessageRetryReason2[(MessageRetryReason2['SignalErrorBadMac'] = 7)] = 'SignalErrorBadMac'
2855
- MessageRetryReason2[(MessageRetryReason2['MessageExpired'] = 8)] = 'MessageExpired'
2856
- MessageRetryReason2[(MessageRetryReason2['DecryptionError'] = 9)] = 'DecryptionError'
2857
- })(MessageRetryReason || (MessageRetryReason = {}))
2858
- var MAC_ERROR_CODES = new Set([
2859
- MessageRetryReason.SignalErrorBadMac,
2860
- MessageRetryReason.SignalErrorInvalidMessage,
2861
- MessageRetryReason.SignalErrorNoSession,
2862
- MessageRetryReason.SignalErrorInvalidKeyId
2863
- ])
2864
- function parseRetryReason(code) {
2865
- if (code === void 0 || code === null) {
2866
- return MessageRetryReason.UnknownError
2867
- }
2868
- const n = typeof code === 'string' ? parseInt(code, 10) : code
2869
- if (isNaN(n)) {
2870
- return MessageRetryReason.UnknownError
2871
- }
2872
- if (Object.values(MessageRetryReason).includes(n)) {
2873
- return n
2874
- }
2875
- return MessageRetryReason.UnknownError
2876
- }
2877
- function isMacError(reason) {
2878
- return MAC_ERROR_CODES.has(reason)
2879
- }
2880
- function getRetryReasonDescription(reason) {
2881
- switch (reason) {
2882
- case MessageRetryReason.UnknownError:
2883
- return 'Unknown error'
2884
- case MessageRetryReason.GenericError:
2885
- return 'Generic error'
2886
- case MessageRetryReason.SignalErrorInvalidKeyId:
2887
- return 'Invalid key ID \u2014 peer prekey rotated'
2888
- case MessageRetryReason.SignalErrorInvalidMessage:
2889
- return 'Invalid message format'
2890
- case MessageRetryReason.SignalErrorNoSession:
2891
- return 'No session \u2014 peer not initialized'
2892
- case MessageRetryReason.SignalErrorBadMac:
2893
- return 'Bad MAC \u2014 encryption session mismatch'
2894
- case MessageRetryReason.MessageExpired:
2895
- return 'Message expired \u2014 too old to decrypt'
2896
- case MessageRetryReason.DecryptionError:
2897
- return 'Decryption failed'
2898
- default:
2899
- return `Unknown reason code ${reason}`
2900
- }
2901
- }
2902
-
2903
-
2904
-
2905
- function mediaFingerprint(media) {
2906
- if (Buffer.isBuffer(media)) {
2907
- const len = media.length
2908
- const head = media.subarray(0, Math.min(24, len)).toString('hex')
2909
- const tail = len > 24 ? media.subarray(Math.max(24, len - 24)).toString('hex') : ''
2910
- return `buf:${len}:${head}:${tail}`
2911
- }
2912
- if (media && typeof media === 'object' && typeof media.url === 'string') {
2913
- return `url:${media.url}`
2914
- }
2915
- if (typeof media === 'string') {
2916
- return `str:${media}`
2917
- }
2918
- if (media && typeof media === 'object') {
2919
-
2920
-
2921
-
2922
- return `unresolvable:${Math.random().toString(36)}`
2923
- }
2924
- return 'none'
2925
- }
2926
-
2927
- function buildContentSignature(content) {
2928
- if (!content || typeof content !== 'object') return String(content ?? '')
2929
- const parts = []
2930
- const text =
2931
- content.text ||
2932
- content.caption ||
2933
- content.image?.caption ||
2934
- content.video?.caption ||
2935
- content.document?.caption ||
2936
- ''
2937
- if (text) parts.push(`text:${text}`)
2938
- if (content.image !== undefined) parts.push(`image:${mediaFingerprint(content.image)}`)
2939
- if (content.video !== undefined) parts.push(`video:${mediaFingerprint(content.video)}`)
2940
- if (content.audio !== undefined) parts.push(`audio:${mediaFingerprint(content.audio)}`)
2941
- if (content.sticker !== undefined) parts.push(`sticker:${mediaFingerprint(content.sticker)}`)
2942
- if (content.document !== undefined) {
2943
- parts.push(`document:${mediaFingerprint(content.document)}:${content.fileName || ''}`)
2944
- }
2945
- if (content.location) {
2946
- parts.push(`location:${content.location.degreesLatitude}:${content.location.degreesLongitude}`)
2947
- }
2948
- if (content.contacts) {
2949
- const vcards = content.contacts.contacts?.map(c => c.vcard).join('|') || JSON.stringify(content.contacts)
2950
- parts.push(`contacts:${vcards}`)
2951
- }
2952
- if (content.poll) {
2953
- parts.push(`poll:${content.poll.name}:${(content.poll.values || []).join(',')}`)
2954
- }
2955
- if (content.buttons) parts.push(`buttons:${JSON.stringify(content.buttons)}`)
2956
- if (content.templateButtons) parts.push(`template:${JSON.stringify(content.templateButtons)}`)
2957
- if (content.sections) parts.push(`sections:${JSON.stringify(content.sections)}`)
2958
- if (content.react) parts.push(`react:${content.react.text}:${content.react.key?.id || ''}`)
2959
- if (!parts.length) {
2960
-
2961
- parts.push(`shape:${Object.keys(content).sort().join(',')}`)
2962
- }
2963
- return parts.join('|')
2964
- }
2965
- function wrapSocket(sock, config, warmUpState, wrapOptions) {
2966
- const antiban = new AntiBan(config, warmUpState)
2967
- const options = {
2968
- autoRespondToIncoming: false,
2969
- ...wrapOptions
2970
- }
2971
-
2972
-
2973
-
2974
-
2975
- const pendingAutoReplyTimers = new Set()
2976
- const scheduleAutoReply = (jid, suggestedText) => {
2977
- const replyDelay = Math.floor(Math.random() * 12e3) + 3e3
2978
- const timer = setTimeout(async () => {
2979
- pendingAutoReplyTimers.delete(timer)
2980
- try {
2981
- await sock.sendMessage(jid, { text: suggestedText })
2982
- } catch (error) {}
2983
- }, replyDelay)
2984
- pendingAutoReplyTimers.add(timer)
2985
- }
2986
- const handleConnectionUpdate = update => {
2987
- if (update.connection === 'close') {
2988
- const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown'
2989
- antiban.onDisconnect(reason)
2990
- }
2991
- if (update.connection === 'open') {
2992
- antiban.onReconnect()
2993
- }
2994
- if (update.reachoutTimeLock) {
2995
- antiban.timelock.onTimelockUpdate({
2996
- isActive: update.reachoutTimeLock.isActive,
2997
- timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
2998
- enforcementType: update.reachoutTimeLock.enforcementType
2999
- })
3000
- }
3001
- }
3002
- const handleMessagesUpdate = updates => {
3003
- for (const update of updates) {
3004
- if (update?.update?.messageStubParameters) {
3005
- const params = update.update.messageStubParameters
3006
- if (params.includes(463) || params.includes('463')) {
3007
- antiban.timelock.record463Error()
3008
- }
3009
- }
3010
- antiban.retryTracker.onMessageUpdate(update)
3011
- }
3012
- antiban.jidCanonicalizer?.onMessageUpdate(updates)
3013
- }
3014
- const handleMessagesUpsert = upsert => {
3015
- const { messages } = upsert
3016
- antiban.jidCanonicalizer?.onIncomingEvent(upsert)
3017
- for (const msg of messages || []) {
3018
- const jid = msg.key?.remoteJid
3019
- if (!jid) continue
3020
- antiban.timelock.registerKnownChat(jid)
3021
- const isSelf = msg.key?.fromMe || false
3022
- if (isSelf) continue
3023
- const msgText =
3024
- msg.message?.conversation ||
3025
- msg.message?.extendedTextMessage?.text ||
3026
- msg.message?.imageMessage?.caption ||
3027
- msg.message?.videoMessage?.caption ||
3028
- ''
3029
- const replySuggestion = antiban.onIncomingMessage(jid, msgText)
3030
- if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
3031
- scheduleAutoReply(jid, replySuggestion.suggestedText)
3032
- }
3033
- }
3034
- }
3035
- let stopEventBridge = () => {}
3036
- if (typeof sock.ev.process === 'function') {
3037
- const processListener = async events => {
3038
- if (events['connection.update']) handleConnectionUpdate(events['connection.update'])
3039
- if (events['messages.update']) handleMessagesUpdate(events['messages.update'])
3040
- if (events['messages.upsert']) handleMessagesUpsert(events['messages.upsert'])
3041
- }
3042
- const maybeUnsubscribe = sock.ev.process(processListener)
3043
- if (typeof maybeUnsubscribe === 'function') {
3044
- stopEventBridge = maybeUnsubscribe
3045
- }
3046
- } else {
3047
- sock.ev.on('connection.update', handleConnectionUpdate)
3048
- sock.ev.on('messages.update', handleMessagesUpdate)
3049
- sock.ev.on('messages.upsert', handleMessagesUpsert)
3050
- stopEventBridge = () => {
3051
- sock.ev.off('connection.update', handleConnectionUpdate)
3052
- sock.ev.off('messages.update', handleMessagesUpdate)
3053
- sock.ev.off('messages.upsert', handleMessagesUpsert)
3054
- }
3055
- }
3056
- const originalSendMessage = sock.sendMessage.bind(sock)
3057
- const wrappedSendMessage = async (jid, content, options2) => {
3058
- const canonicalJid = antiban.jidCanonicalizer?.canonicalizeTarget(jid) || jid
3059
- const text =
3060
- content?.text ||
3061
- content?.caption ||
3062
- content?.image?.caption ||
3063
- content?.video?.caption ||
3064
- content?.document?.caption ||
3065
- ''
3066
- const dedupKey = buildContentSignature(content)
3067
- const decision = await antiban.beforeSend(canonicalJid, text, dedupKey)
3068
- if (!decision.allowed) {
3069
- throw new Error(`[baileys-antiban] Message blocked: ${decision.reason}`)
3070
- }
3071
- if (decision.delayMs > 0) {
3072
- await new Promise(resolve => setTimeout(resolve, decision.delayMs))
3073
- }
3074
- try {
3075
- const result = await originalSendMessage(canonicalJid, content, options2)
3076
- antiban.afterSend(canonicalJid, text, dedupKey)
3077
- antiban.timelock.registerKnownChat(canonicalJid)
3078
- if (result?.key?.id) {
3079
- antiban.retryTracker.clear(result.key.id)
3080
- }
3081
- return result
3082
- } catch (error) {
3083
- antiban.afterSendFailed(error instanceof Error ? error.message : String(error))
3084
- throw error
3085
- }
3086
- }
3087
- const wrapped = Object.create(sock)
3088
- wrapped.sendMessage = wrappedSendMessage
3089
- wrapped.antiban = antiban
3090
- const originalDestroy = antiban.destroy.bind(antiban)
3091
- wrapped.antiban.destroy = () => {
3092
- stopEventBridge()
3093
- for (const timer of pendingAutoReplyTimers) clearTimeout(timer)
3094
- pendingAutoReplyTimers.clear()
3095
- originalDestroy()
3096
- }
3097
- return wrapped
3098
- }
3099
-
3100
-
3101
- var import_events = require('events')
3102
- var DEFAULT_CONFIG12 = {
3103
- maxAttempts: 3,
3104
- retryBaseDelayMs: 3e4,
3105
- maxQueueSize: 1e3,
3106
- priorityOrder: true
3107
- }
3108
- var MessageQueue = class extends import_events.EventEmitter {
3109
- config
3110
- queue = []
3111
- processing = false
3112
- sendFn = null
3113
- drainTimer = null
3114
- idCounter = 0
3115
- constructor(config = {}) {
3116
- super()
3117
- this.config = { ...DEFAULT_CONFIG12, ...config }
3118
- }
3119
-
3120
- setSendFunction(fn) {
3121
- this.sendFn = fn
3122
- }
3123
-
3124
- add(recipient, content, options) {
3125
- if (this.queue.length >= this.config.maxQueueSize) {
3126
- throw new Error(`Queue full (${this.config.maxQueueSize} messages)`)
3127
- }
3128
- const id = `msg_${Date.now()}_${++this.idCounter}`
3129
- const message = {
3130
- id,
3131
- recipient,
3132
- content,
3133
- priority: options?.priority || 'normal',
3134
- addedAt: Date.now(),
3135
- attempts: 0,
3136
- maxAttempts: this.config.maxAttempts,
3137
- scheduledFor: options?.scheduledFor?.getTime(),
3138
- metadata: options?.metadata
3139
- }
3140
- this.queue.push(message)
3141
- this.sortQueue()
3142
- this.emit('added', message)
3143
- return id
3144
- }
3145
-
3146
- addBulk(recipients, content, options) {
3147
- return recipients.map(r => this.add(r, content, options))
3148
- }
3149
-
3150
- start(intervalMs = 1e3) {
3151
- if (this.drainTimer) return
3152
- this.drainTimer = setInterval(() => this.processNext(), intervalMs)
3153
- this.emit('started')
3154
- }
3155
-
3156
- stop() {
3157
- if (this.drainTimer) {
3158
- clearInterval(this.drainTimer)
3159
- this.drainTimer = null
3160
- }
3161
- this.emit('stopped')
3162
- }
3163
-
3164
- destroy() {
3165
- this.stop()
3166
- }
3167
-
3168
- async processNext() {
3169
- if (this.processing || !this.sendFn) return
3170
- const now = Date.now()
3171
- const message = this.queue.find(m => !m.scheduledFor || m.scheduledFor <= now)
3172
- if (!message) return
3173
- this.processing = true
3174
- try {
3175
- message.attempts++
3176
- await this.sendFn(message.recipient, message.content)
3177
- this.queue = this.queue.filter(m => m.id !== message.id)
3178
- this.emit('sent', message)
3179
- } catch (err) {
3180
- message.lastError = err.message
3181
- if (err.message?.includes('baileys-antiban')) {
3182
- message.attempts--
3183
- this.emit('delayed', message, err.message)
3184
- } else if (message.attempts >= message.maxAttempts) {
3185
- this.queue = this.queue.filter(m => m.id !== message.id)
3186
- this.emit('failed', message, err.message)
3187
- } else {
3188
- const backoff = this.config.retryBaseDelayMs * Math.pow(2, message.attempts - 1)
3189
- message.scheduledFor = Date.now() + backoff
3190
- this.emit('retry', message, message.attempts, backoff)
3191
- }
3192
- } finally {
3193
- this.processing = false
3194
- }
3195
- }
3196
-
3197
- getStats() {
3198
- const now = Date.now()
3199
- return {
3200
- total: this.queue.length,
3201
- pending: this.queue.filter(m => !m.scheduledFor || m.scheduledFor <= now).length,
3202
- scheduled: this.queue.filter(m => m.scheduledFor && m.scheduledFor > now).length,
3203
- byPriority: {
3204
- high: this.queue.filter(m => m.priority === 'high').length,
3205
- normal: this.queue.filter(m => m.priority === 'normal').length,
3206
- low: this.queue.filter(m => m.priority === 'low').length
3207
- },
3208
- processing: this.processing,
3209
- isRunning: this.drainTimer !== null
3210
- }
3211
- }
3212
-
3213
- clear() {
3214
- const count = this.queue.length
3215
- this.queue = []
3216
- this.emit('cleared', count)
3217
- }
3218
-
3219
- remove(id) {
3220
- const before = this.queue.length
3221
- this.queue = this.queue.filter(m => m.id !== id)
3222
- return this.queue.length < before
3223
- }
3224
-
3225
- export() {
3226
- return [...this.queue]
3227
- }
3228
-
3229
- import(messages) {
3230
- this.queue = [...messages]
3231
- this.sortQueue()
3232
- }
3233
- sortQueue() {
3234
- if (!this.config.priorityOrder) return
3235
- const priorityWeight = { high: 0, normal: 1, low: 2 }
3236
- this.queue.sort((a, b) => {
3237
- const pDiff = priorityWeight[a.priority] - priorityWeight[b.priority]
3238
- if (pDiff !== 0) return pDiff
3239
- return a.addedAt - b.addedAt
3240
- })
3241
- }
3242
- }
3243
-
3244
-
3245
- var DEFAULT_CONFIG13 = {
3246
- zeroWidthChars: true,
3247
- punctuationVariation: true,
3248
- emojiPadding: false,
3249
- synonyms: false
3250
- }
3251
- var ZERO_WIDTH = [
3252
- '\u200B',
3253
-
3254
- '\u200C',
3255
-
3256
- '\u200D',
3257
-
3258
- '\uFEFF'
3259
-
3260
- ]
3261
- var SYNONYMS = {
3262
- hello: ['hi', 'hey', 'howdy'],
3263
- hi: ['hello', 'hey', 'howdy'],
3264
- thanks: ['thank you', 'thx', 'cheers'],
3265
- please: ['kindly', 'pls'],
3266
- great: ['awesome', 'excellent', 'wonderful'],
3267
- good: ['great', 'nice', 'fine'],
3268
- buy: ['purchase', 'get', 'grab'],
3269
- sell: ['offer', 'list'],
3270
- price: ['cost', 'amount', 'value'],
3271
- available: ['in stock', 'on offer'],
3272
- check: ['look at', 'see', 'view'],
3273
- join: ['participate', 'enter', 'come to'],
3274
- start: ['begin', 'kick off', 'commence'],
3275
- end: ['finish', 'close', 'conclude'],
3276
- bid: ['offer', 'place a bid'],
3277
- win: ['secure', 'take home'],
3278
- item: ['lot', 'piece', 'product']
3279
- }
3280
- var ContentVariator = class {
3281
- config
3282
- counter = 0
3283
- constructor(config = {}) {
3284
- this.config = { ...DEFAULT_CONFIG13, ...config }
3285
- }
3286
-
3287
- vary(text) {
3288
- let result = text
3289
- this.counter++
3290
- if (this.config.customVariator) {
3291
- return this.config.customVariator(result, this.counter)
3292
- }
3293
- if (this.config.synonyms) {
3294
- result = this.applySynonyms(result)
3295
- }
3296
- if (this.config.zeroWidthChars) {
3297
- result = this.addZeroWidth(result)
3298
- }
3299
- if (this.config.punctuationVariation) {
3300
- result = this.varyPunctuation(result)
3301
- }
3302
- if (this.config.emojiPadding) {
3303
- result = this.addEmojiPadding(result)
3304
- }
3305
- return result
3306
- }
3307
-
3308
- varyBulk(text, count) {
3309
- const results = []
3310
- const startCounter = this.counter
3311
- for (let i = 0; i < count; i++) {
3312
- this.counter = startCounter + i
3313
- results.push(this.vary(text))
3314
- }
3315
- this.counter = startCounter + count
3316
- return results
3317
- }
3318
- addZeroWidth(text) {
3319
- const words = text.split(' ')
3320
- if (words.length < 2) return text
3321
- const positions = this.randomPositions(words.length - 1, Math.min(2, words.length - 1))
3322
- return words
3323
- .map((word, i) => {
3324
- if (positions.includes(i)) {
3325
- const zwc = ZERO_WIDTH[Math.floor(Math.random() * ZERO_WIDTH.length)]
3326
- return word + zwc
3327
- }
3328
- return word
3329
- })
3330
- .join(' ')
3331
- }
3332
- varyPunctuation(text) {
3333
- const variations = [
3334
-
3335
- () => text + ' ',
3336
- () => text + ' ',
3337
-
3338
- () => (text.endsWith('.') ? text.slice(0, -1) : text + '.'),
3339
-
3340
- () => text,
3341
-
3342
- () => (text.charAt(0) === text.charAt(0).toUpperCase() ? text.charAt(0).toLowerCase() + text.slice(1) : text)
3343
- ]
3344
- return variations[this.counter % variations.length]()
3345
- }
3346
- addEmojiPadding(text) {
3347
- const emojis = ['', ' \u{1F44D}', ' \u2705', ' \u{1F4CC}', ' \u{1F4AC}', ' \u{1F4E2}']
3348
- return text + emojis[this.counter % emojis.length]
3349
- }
3350
- applySynonyms(text) {
3351
- const lower = text.toLowerCase()
3352
- for (const key of Object.keys(SYNONYMS)) {
3353
- const idx = lower.indexOf(key)
3354
- if (idx === -1) continue
3355
- const word = text.substring(idx, idx + key.length)
3356
- if (!SYNONYMS[key] || Math.random() > 0.5) continue
3357
- const synonym = SYNONYMS[key][Math.floor(Math.random() * SYNONYMS[key].length)]
3358
- const replacement =
3359
- word[0] === word[0].toUpperCase() ? synonym.charAt(0).toUpperCase() + synonym.slice(1) : synonym
3360
- return text.substring(0, idx) + replacement + text.substring(idx + key.length)
3361
- }
3362
- return text
3363
- }
3364
- randomPositions(max, count) {
3365
- const positions = []
3366
- while (positions.length < count) {
3367
- const pos = Math.floor(Math.random() * max)
3368
- if (!positions.includes(pos)) positions.push(pos)
3369
- }
3370
- return positions
3371
- }
3372
- }
3373
-
3374
-
3375
- var DEFAULT_CONFIG14 = {
3376
- urls: [],
3377
- minRiskLevel: 'medium',
3378
- cooldownMs: 3e5,
3379
- includeStats: true
3380
- }
3381
- var WebhookAlerts = class {
3382
- config
3383
- lastAlertTime = 0
3384
- constructor(config = {}) {
3385
- this.config = { ...DEFAULT_CONFIG14, ...config }
3386
- }
3387
-
3388
- async alert(data) {
3389
- const riskOrder = ['low', 'medium', 'high', 'critical']
3390
- if (riskOrder.indexOf(data.risk) < riskOrder.indexOf(this.config.minRiskLevel)) {
3391
- return
3392
- }
3393
- const now = Date.now()
3394
- if (now - this.lastAlertTime < this.config.cooldownMs) {
3395
- return
3396
- }
3397
- this.lastAlertTime = now
3398
- const payload = {
3399
- source: 'baileys-antiban',
3400
- timestamp: new Date().toISOString(),
3401
- ...data
3402
- }
3403
- for (const url of this.config.urls) {
3404
- this.postWebhook(url, payload).catch(() => {})
3405
- }
3406
- if (this.config.telegram) {
3407
- const emoji =
3408
- { low: '\u{1F7E2}', medium: '\u{1F7E1}', high: '\u{1F7E0}', critical: '\u{1F534}' }[data.risk] || '\u26AA'
3409
- const text = `${emoji} *baileys-antiban Alert*
3410
-
3411
- Risk: *${data.risk.toUpperCase()}* (score: ${data.score})
3412
- ${data.recommendation}
3413
-
3414
- Reasons:
3415
- ${data.reasons.map(r => `\u2022 ${r}`).join('\n')}`
3416
- this.postWebhook(`https://api.telegram.org/bot${this.config.telegram.botToken}/sendMessage`, {
3417
- chat_id: this.config.telegram.chatId,
3418
- text,
3419
- parse_mode: 'Markdown'
3420
- }).catch(() => {})
3421
- }
3422
- if (this.config.discord) {
3423
- const color = { low: 65280, medium: 16776960, high: 16746496, critical: 16711680 }[data.risk] || 0
3424
- this.postWebhook(this.config.discord.webhookUrl, {
3425
- embeds: [
3426
- {
3427
- title: '\u{1F6E1}\uFE0F baileys-antiban Alert',
3428
- color,
3429
- fields: [
3430
- { name: 'Risk', value: data.risk.toUpperCase(), inline: true },
3431
- { name: 'Score', value: String(data.score), inline: true },
3432
- { name: 'Recommendation', value: data.recommendation },
3433
- { name: 'Reasons', value: data.reasons.join('\n') }
3434
- ],
3435
- timestamp: new Date().toISOString()
3436
- }
3437
- ]
3438
- }).catch(() => {})
3439
- }
3440
- }
3441
- async postWebhook(url, payload) {
3442
- try {
3443
- const response = await fetch(url, {
3444
- method: 'POST',
3445
- headers: {
3446
- 'Content-Type': 'application/json',
3447
- ...this.config.headers
3448
- },
3449
- body: JSON.stringify(payload)
3450
- })
3451
- if (!response.ok) {
3452
- process.stderr.write(`[baileys-antiban] Webhook failed: ${response.status}\n`)
3453
- }
3454
- } catch (err) {
3455
- process.stderr.write(`[baileys-antiban] Webhook error: ${err}\n`)
3456
- }
3457
- }
3458
- }
3459
-
3460
-
3461
- var DEFAULT_CONFIG15 = {
3462
- timezone: 'UTC',
3463
- activeHours: [8, 21],
3464
- weekendFactor: 0.5,
3465
- peakHours: [10, 14],
3466
- peakFactor: 1.3,
3467
- lunchBreak: [12, 13],
3468
- lunchFactor: 0.5
3469
- }
3470
- var Scheduler = class {
3471
- config
3472
- constructor(config = {}) {
3473
- this.config = { ...DEFAULT_CONFIG15, ...config }
3474
- }
3475
-
3476
- isActiveTime() {
3477
- const hour = this.getCurrentHour()
3478
- const [start, end] = this.config.activeHours
3479
- return hour >= start && hour < end
3480
- }
3481
-
3482
- getSpeedFactor() {
3483
- if (!this.isActiveTime()) return 0
3484
- const hour = this.getCurrentHour()
3485
- const day = this.getCurrentDay()
3486
- let factor = 1
3487
- if (day === 0 || day === 6) {
3488
- factor *= this.config.weekendFactor
3489
- }
3490
- const [peakStart, peakEnd] = this.config.peakHours
3491
- if (hour >= peakStart && hour < peakEnd) {
3492
- factor *= this.config.peakFactor
3493
- }
3494
- const [lunchStart, lunchEnd] = this.config.lunchBreak
3495
- if (hour >= lunchStart && hour < lunchEnd) {
3496
- factor *= this.config.lunchFactor
3497
- }
3498
- return factor
3499
- }
3500
-
3501
- msUntilActive() {
3502
- if (this.isActiveTime()) return 0
3503
- const now = new Date()
3504
- const hour = now.getHours()
3505
- const [start] = this.config.activeHours
3506
- let nextActive
3507
- if (hour >= this.config.activeHours[1]) {
3508
- nextActive = new Date(now)
3509
- nextActive.setDate(nextActive.getDate() + 1)
3510
- nextActive.setHours(start, 0, 0, 0)
3511
- } else {
3512
- nextActive = new Date(now)
3513
- nextActive.setHours(start, 0, 0, 0)
3514
- }
3515
- return nextActive.getTime() - now.getTime()
3516
- }
3517
-
3518
- adjustDelay(baseDelayMs) {
3519
- const factor = this.getSpeedFactor()
3520
- if (factor === 0) return -1
3521
- return Math.round(baseDelayMs / factor)
3522
- }
3523
-
3524
- getStatus() {
3525
- const hour = this.getCurrentHour()
3526
- const day = this.getCurrentDay()
3527
- const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
3528
- return {
3529
- active: this.isActiveTime(),
3530
- currentHour: hour,
3531
- day: dayNames[day],
3532
- isWeekend: day === 0 || day === 6,
3533
- speedFactor: this.getSpeedFactor(),
3534
- msUntilActive: this.msUntilActive(),
3535
- activeWindow: `${this.config.activeHours[0]}:00 - ${this.config.activeHours[1]}:00`
3536
- }
3537
- }
3538
- getCurrentHour() {
3539
- return new Date().getHours()
3540
- }
3541
- getCurrentDay() {
3542
- return new Date().getDay()
3543
- }
3544
- }
3545
-
3546
-
3547
- var _fsPromises = require('fs/promises')
3548
- var _fsSync = require('fs')
3549
- var _path3 = require('path')
3550
- var FileStateAdapter = class {
3551
- basePath
3552
- constructor(basePath) {
3553
- this.basePath = basePath
3554
- }
3555
- async save(key, state) {
3556
- const filePath = _path3.join(this.basePath, `${key}.json`)
3557
- await _fsPromises.mkdir(this.basePath, { recursive: true })
3558
- await _fsPromises.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8')
3559
- }
3560
- async load(key) {
3561
- const filePath = _path3.join(this.basePath, `${key}.json`)
3562
- try {
3563
- const data = await _fsPromises.readFile(filePath, 'utf-8')
3564
- return JSON.parse(data)
3565
- } catch (err) {
3566
- if (err.code === 'ENOENT') return null
3567
- throw err
3568
- }
3569
- }
3570
- async delete(key) {
3571
- const filePath = _path3.join(this.basePath, `${key}.json`)
3572
- try {
3573
- await _fsPromises.unlink(filePath)
3574
- } catch (err) {
3575
- if (err.code !== 'ENOENT') throw err
3576
- }
3577
- }
3578
- async list() {
3579
- try {
3580
- const files = await _fsPromises.readdir(this.basePath)
3581
- return files.filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, ''))
3582
- } catch (err) {
3583
- if (err.code === 'ENOENT') return []
3584
- throw err
3585
- }
3586
- }
3587
- }
3588
-
3589
-
3590
- var DEFAULT_CONFIG16 = {
3591
- maxTrackedChats: 1e3,
3592
- maxGapMs: 30 * 6e4,
3593
-
3594
- persistDebounceMs: 2e3,
3595
- onGapFilled: () => {},
3596
- logger: {
3597
- info: () => {},
3598
- warn: () => {},
3599
- error: () => {}
3600
- }
3601
- }
3602
- function messageRecovery(sock, config) {
3603
- const cfg = { ...DEFAULT_CONFIG16, ...config }
3604
- const logger = cfg.logger
3605
- const lastSeen = new Map()
3606
- let disconnectedAt = null
3607
- let totalRecovered = 0
3608
- let lastReconnectAt = null
3609
- let lastGapMs = null
3610
- let persistTimer = null
3611
- let loggedFetchWarning = false
3612
- if (cfg.persistPath) {
3613
- loadPersistence()
3614
- }
3615
- const messagesListener = sock.ev.process ? setupProcessListener() : setupLegacyListener()
3616
- const connectionListener = update => {
3617
- if (update.connection === 'close') {
3618
- disconnectedAt = Date.now()
3619
- logger.info?.(`[messageRecovery] Disconnected at ${new Date(disconnectedAt).toISOString()}`)
3620
- }
3621
- if (update.connection === 'open' && disconnectedAt !== null) {
3622
- void recoverMessages()
3623
- }
3624
- }
3625
- sock.ev.on('connection.update', connectionListener)
3626
- function setupProcessListener() {
3627
- const listener = async events => {
3628
- if (events['messages.upsert']) {
3629
- const { messages, type } = events['messages.upsert']
3630
- if (type === 'notify') {
3631
- for (const msg of messages || []) {
3632
- trackMessage(msg)
3633
- }
3634
- }
3635
- }
3636
- }
3637
- sock.ev.process(listener)
3638
- return listener
3639
- }
3640
- function setupLegacyListener() {
3641
- const listener = upsert => {
3642
- const { messages, type } = upsert
3643
- if (type === 'notify') {
3644
- for (const msg of messages || []) {
3645
- trackMessage(msg)
3646
- }
3647
- }
3648
- }
3649
- sock.ev.on('messages.upsert', listener)
3650
- return listener
3651
- }
3652
- function trackMessage(msg) {
3653
- const jid = msg.key?.remoteJid
3654
- const messageId = msg.key?.id
3655
- const timestamp = msg.messageTimestamp
3656
- if (!jid || !messageId || !timestamp) return
3657
- if (msg.key?.fromMe) return
3658
- const now = Date.now()
3659
- lastSeen.set(jid, {
3660
- messageId,
3661
- timestamp: typeof timestamp === 'number' ? timestamp : parseInt(timestamp, 10),
3662
- lastTouchedAt: now
3663
- })
3664
- if (lastSeen.size > cfg.maxTrackedChats) {
3665
- evictOldest()
3666
- }
3667
- schedulePersist()
3668
- }
3669
- function evictOldest() {
3670
- let oldestJid = null
3671
- let oldestTime = Infinity
3672
- for (const [jid, entry] of lastSeen) {
3673
- if (entry.lastTouchedAt < oldestTime) {
3674
- oldestTime = entry.lastTouchedAt
3675
- oldestJid = jid
3676
- }
3677
- }
3678
- if (oldestJid) {
3679
- lastSeen.delete(oldestJid)
3680
- }
3681
- }
3682
- async function recoverMessages() {
3683
- const recoveryStartMs = Date.now()
3684
- const gapMs = recoveryStartMs - disconnectedAt
3685
- logger.info?.(`[messageRecovery] Reconnected after ${(gapMs / 1e3).toFixed(1)}s`)
3686
- if (gapMs > cfg.maxGapMs) {
3687
- logger.warn?.(
3688
- `[messageRecovery] Gap too large (${(gapMs / 1e3).toFixed(0)}s > ${(cfg.maxGapMs / 1e3).toFixed(0)}s) \u2014 skipping recovery`
3689
- )
3690
- disconnectedAt = null
3691
- lastGapMs = gapMs
3692
- await cfg.onGapTooLarge?.(gapMs)
3693
- return
3694
- }
3695
- let recovered = 0
3696
- const chatsToRecover = Array.from(lastSeen.entries())
3697
- if (typeof sock.fetchMessageHistory !== 'function') {
3698
- if (!loggedFetchWarning) {
3699
- logger.warn?.(
3700
- `[messageRecovery] sock.fetchMessageHistory not available \u2014 recovery disabled. Baileys version may not support history fetch. User must implement manual reconciliation.`
3701
- )
3702
- loggedFetchWarning = true
3703
- }
3704
- disconnectedAt = null
3705
- lastReconnectAt = new Date()
3706
- lastGapMs = gapMs
3707
- await cfg.onRecoveryComplete?.({
3708
- chats: 0,
3709
- recovered: 0,
3710
- durationMs: Date.now() - recoveryStartMs
3711
- })
3712
- return
3713
- }
3714
- for (const [jid, lastSeenEntry] of chatsToRecover) {
3715
- try {
3716
- const messages = await sock.fetchMessageHistory(jid, 50, {
3717
- before: void 0
3718
-
3719
- })
3720
- if (!messages || !Array.isArray(messages)) continue
3721
- const gapMessages = messages.filter(msg => {
3722
- const ts = msg.messageTimestamp
3723
- if (!ts) return false
3724
- const msgTs = typeof ts === 'number' ? ts : parseInt(ts, 10)
3725
- return msgTs > lastSeenEntry.timestamp
3726
- })
3727
- gapMessages.sort((a, b) => {
3728
- const aTs = typeof a.messageTimestamp === 'number' ? a.messageTimestamp : parseInt(a.messageTimestamp, 10)
3729
- const bTs = typeof b.messageTimestamp === 'number' ? b.messageTimestamp : parseInt(b.messageTimestamp, 10)
3730
- return aTs - bTs
3731
- })
3732
- for (const msg of gapMessages) {
3733
- await cfg.onGapFilled(msg, jid)
3734
- recovered++
3735
- const msgTs =
3736
- typeof msg.messageTimestamp === 'number' ? msg.messageTimestamp : parseInt(msg.messageTimestamp, 10)
3737
- if (msgTs > lastSeenEntry.timestamp) {
3738
- lastSeenEntry.timestamp = msgTs
3739
- lastSeenEntry.messageId = msg.key?.id || lastSeenEntry.messageId
3740
- lastSeenEntry.lastTouchedAt = Date.now()
3741
- }
3742
- }
3743
- if (gapMessages.length > 0) {
3744
- logger.info?.(`[messageRecovery] Recovered ${gapMessages.length} messages from ${jid}`)
3745
- }
3746
- } catch (err) {
3747
- logger.error?.(`[messageRecovery] Failed to recover from ${jid}: ${err.message}`)
3748
- }
3749
- }
3750
- totalRecovered += recovered
3751
- lastReconnectAt = new Date()
3752
- lastGapMs = gapMs
3753
- disconnectedAt = null
3754
- logger.info?.(
3755
- `[messageRecovery] Recovery complete: ${recovered} messages across ${chatsToRecover.length} chats in ${Date.now() - recoveryStartMs}ms`
3756
- )
3757
- await cfg.onRecoveryComplete?.({
3758
- chats: chatsToRecover.length,
3759
- recovered,
3760
- durationMs: Date.now() - recoveryStartMs
3761
- })
3762
- }
3763
- function schedulePersist() {
3764
- if (!cfg.persistPath) return
3765
- if (persistTimer) {
3766
- clearTimeout(persistTimer)
3767
- }
3768
- persistTimer = setTimeout(() => {
3769
- void flushPersistence()
3770
- }, cfg.persistDebounceMs)
3771
- }
3772
- async function flushPersistence() {
3773
- if (!cfg.persistPath) return
3774
- try {
3775
- const data = {}
3776
- for (const [jid, entry] of lastSeen) {
3777
- data[jid] = {
3778
- id: entry.messageId,
3779
- timestamp: entry.timestamp
3780
- }
3781
- }
3782
- await _fsPromises.writeFile(cfg.persistPath, JSON.stringify(data, null, 2), 'utf-8')
3783
- } catch (err) {
3784
- logger.error?.(`[messageRecovery] Failed to persist state: ${err.message}`)
3785
- }
3786
- }
3787
- function loadPersistence() {
3788
- if (!cfg.persistPath) return
3789
- try {
3790
- const fs4 = require('fs')
3791
- if (!fs4.existsSync(cfg.persistPath)) return
3792
- const raw = fs4.readFileSync(cfg.persistPath, 'utf-8')
3793
- const data = JSON.parse(raw)
3794
- for (const [jid, entry] of Object.entries(data)) {
3795
- lastSeen.set(jid, {
3796
- messageId: entry.id,
3797
- timestamp: entry.timestamp,
3798
- lastTouchedAt: Date.now()
3799
- })
3800
- }
3801
- logger.info?.(`[messageRecovery] Loaded ${lastSeen.size} entries from ${cfg.persistPath}`)
3802
- } catch (err) {
3803
- logger.warn?.(`[messageRecovery] Failed to load persisted state: ${err.message}`)
3804
- }
3805
- }
3806
- return {
3807
- async stop() {
3808
- sock.ev.off('connection.update', connectionListener)
3809
- if (!sock.ev.process) {
3810
- sock.ev.off('messages.upsert', messagesListener)
3811
- }
3812
- if (persistTimer) {
3813
- clearTimeout(persistTimer)
3814
- persistTimer = null
3815
- }
3816
- await flushPersistence()
3817
- logger.info?.(`[messageRecovery] Stopped \u2014 total recovered: ${totalRecovered}`)
3818
- },
3819
- markSeen(chatJid, messageId, timestamp) {
3820
- lastSeen.set(chatJid, {
3821
- messageId,
3822
- timestamp,
3823
- lastTouchedAt: Date.now()
3824
- })
3825
- schedulePersist()
3826
- },
3827
- getStats() {
3828
- return {
3829
- trackedChats: lastSeen.size,
3830
- totalRecovered,
3831
- lastReconnectAt,
3832
- lastGapMs
3833
- }
3834
- }
3835
- }
3836
- }
3837
-
3838
-
3839
- var DEFAULT_APP_VERSION_POOL = [
3840
- [2, 25, 10, 67],
3841
- [2, 25, 10, 68],
3842
- [2, 25, 9, 96],
3843
- [2, 25, 8, 77],
3844
- [2, 25, 7, 85],
3845
- [2, 25, 6, 98],
3846
- [2, 24, 22, 78],
3847
- [2, 24, 20, 86]
3848
- ]
3849
- var DEFAULT_OS_VERSION_POOL = ['11', '12', '13', '14', '15']
3850
- var DEFAULT_DEVICE_MODEL_POOL = [
3851
- 'Pixel 8',
3852
- 'Pixel 9',
3853
- 'Pixel 7',
3854
- 'Galaxy S24',
3855
- 'Galaxy S23',
3856
- 'Galaxy S22',
3857
- 'Xiaomi 14',
3858
- 'Xiaomi 13',
3859
- 'OnePlus 12',
3860
- 'OnePlus 11',
3861
- 'Moto G84',
3862
- 'Realme 12',
3863
- 'Vivo V30',
3864
- 'Oppo Find X7'
3865
- ]
3866
- var SeededRandom = class {
3867
- state
3868
- constructor(seed) {
3869
- let hash = 0
3870
- for (let i = 0; i < seed.length; i++) {
3871
- hash = (hash << 5) - hash + seed.charCodeAt(i)
3872
- hash = hash & hash
3873
- }
3874
- this.state = Math.abs(hash) || 1
3875
- }
3876
- next() {
3877
- let t = (this.state += 1831565813)
3878
- t = Math.imul(t ^ (t >>> 15), t | 1)
3879
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
3880
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296
3881
- }
3882
- pick(array) {
3883
- return array[Math.floor(this.next() * array.length)]
3884
- }
3885
- }
3886
- function generateFingerprint(config = {}, sessionId) {
3887
- const {
3888
- enabled = true,
3889
- randomizeAppVersion = true,
3890
- randomizeOsVersion = true,
3891
- randomizeDeviceModel = true,
3892
- seed,
3893
- appVersionPool = DEFAULT_APP_VERSION_POOL,
3894
- osVersionPool = DEFAULT_OS_VERSION_POOL,
3895
- deviceModelPool = DEFAULT_DEVICE_MODEL_POOL
3896
- } = config
3897
- const finalSessionId = sessionId || `session-${Date.now()}-${Math.random()}`
3898
- const rng = new SeededRandom(seed || finalSessionId)
3899
- const appVersion = enabled && randomizeAppVersion ? rng.pick(appVersionPool) : appVersionPool[0]
3900
- const osVersion = enabled && randomizeOsVersion ? rng.pick(osVersionPool) : osVersionPool[0]
3901
- const deviceModel = enabled && randomizeDeviceModel ? rng.pick(deviceModelPool) : deviceModelPool[0]
3902
- return {
3903
- appVersion: [...appVersion],
3904
-
3905
- osVersion,
3906
- deviceModel,
3907
- sessionId: finalSessionId
3908
- }
3909
- }
3910
- function applyFingerprint(socketConfig, fp) {
3911
- const config = { ...socketConfig }
3912
- if (config.version !== void 0 || 'version' in config || true) {
3913
- config.version = fp.appVersion
3914
- }
3915
- if (config.browser !== void 0 || 'browser' in config || true) {
3916
- config.browser = [fp.deviceModel, fp.osVersion, `WhatsApp/${fp.appVersion.join('.')}`]
3917
- }
3918
- return config
3919
- }
3920
-
3921
-
3922
- var import_fs = require('fs')
3923
- var path2 = __toESM(require('path'), 1)
3924
- var noop = () => {}
3925
- function credsSnapshot(config) {
3926
- const { credsPath, snapshotDir = path2.join(path2.dirname(credsPath), '.snapshots'), keep = 3, logger = {} } = config
3927
- const log = {
3928
- info: logger.info || noop,
3929
- warn: logger.warn || noop,
3930
- error: logger.error || noop
3931
- }
3932
- async function take() {
3933
- try {
3934
- try {
3935
- await import_fs.promises.access(credsPath)
3936
- } catch {
3937
- log.warn(`[credsSnapshot] Creds file not found: ${credsPath}`)
3938
- return null
3939
- }
3940
- await import_fs.promises.mkdir(snapshotDir, { recursive: true })
3941
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
3942
- const snapshotPath = path2.join(snapshotDir, `creds-${timestamp}.json`)
3943
- const tmpPath = `${snapshotPath}.tmp`
3944
- await import_fs.promises.copyFile(credsPath, tmpPath)
3945
- await import_fs.promises.rename(tmpPath, snapshotPath)
3946
- log.info(`[credsSnapshot] Snapshot taken: ${snapshotPath}`)
3947
- await rotate()
3948
- return snapshotPath
3949
- } catch (err) {
3950
- log.error(`[credsSnapshot] Failed to take snapshot: ${err}`)
3951
- return null
3952
- }
3953
- }
3954
- async function rotate() {
3955
- try {
3956
- const snapshots = await list()
3957
- const toDelete = snapshots.slice(keep)
3958
- for (const snap of toDelete) {
3959
- await import_fs.promises.unlink(snap.path)
3960
- log.info(`[credsSnapshot] Rotated out: ${snap.path}`)
3961
- }
3962
- } catch (err) {
3963
- log.error(`[credsSnapshot] Rotation failed: ${err}`)
3964
- }
3965
- }
3966
- async function list() {
3967
- try {
3968
- await import_fs.promises.access(snapshotDir)
3969
- } catch {
3970
- return []
3971
- }
3972
- try {
3973
- const files = await import_fs.promises.readdir(snapshotDir)
3974
- const snapshots = await Promise.all(
3975
- files
3976
- .filter(f => f.startsWith('creds-') && f.endsWith('.json'))
3977
- .map(async f => {
3978
- const fullPath = path2.join(snapshotDir, f)
3979
- const stat = await import_fs.promises.stat(fullPath)
3980
- return {
3981
- path: fullPath,
3982
- takenAt: stat.mtime,
3983
- size: stat.size
3984
- }
3985
- })
3986
- )
3987
- return snapshots.sort((a, b) => b.takenAt.getTime() - a.takenAt.getTime())
3988
- } catch (err) {
3989
- log.error(`[credsSnapshot] Failed to list snapshots: ${err}`)
3990
- return []
3991
- }
3992
- }
3993
- async function restoreLatest() {
3994
- const snapshots = await list()
3995
- if (snapshots.length === 0) {
3996
- log.warn('[credsSnapshot] No snapshots available to restore')
3997
- return false
3998
- }
3999
- return restore(snapshots[0].path)
4000
- }
4001
- async function restore(snapshotPath) {
4002
- try {
4003
- await import_fs.promises.access(snapshotPath)
4004
- const tmpPath = `${credsPath}.tmp`
4005
- await import_fs.promises.copyFile(snapshotPath, tmpPath)
4006
- await import_fs.promises.rename(tmpPath, credsPath)
4007
- log.info(`[credsSnapshot] Restored from: ${snapshotPath}`)
4008
- return true
4009
- } catch (err) {
4010
- log.error(`[credsSnapshot] Failed to restore from ${snapshotPath}: ${err}`)
4011
- return false
4012
- }
4013
- }
4014
- return {
4015
- take,
4016
- restoreLatest,
4017
- restore,
4018
- list
4019
- }
4020
- }
4021
-
4022
-
4023
- function gaussianRandom() {
4024
- let u = 0
4025
- let v = 0
4026
- while (u === 0) u = Math.random()
4027
- while (v === 0) v = Math.random()
4028
- return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v)
4029
- }
4030
- function readReceiptVariance(config = {}) {
4031
- const { meanMs = 1500, stdDevMs = 800, minMs = 200, maxMs = 8e3, skipIfOlderThanMs = 6e4 } = config
4032
- const pendingTimers = new Set()
4033
- function delayMs() {
4034
- const gaussian = gaussianRandom()
4035
- const value = meanMs + gaussian * stdDevMs
4036
- return Math.max(minMs, Math.min(maxMs, value))
4037
- }
4038
- function wrap(sock) {
4039
- const originalReadMessages = sock.readMessages.bind(sock)
4040
- const wrappedReadMessages = async keys => {
4041
- const now = Date.now()
4042
- const oldMessages = keys.every(key => {
4043
- if (!key.messageTimestamp) return false
4044
- const msgTime =
4045
- typeof key.messageTimestamp === 'number'
4046
- ? key.messageTimestamp * 1e3
4047
- : parseInt(key.messageTimestamp, 10) * 1e3
4048
- return now - msgTime > skipIfOlderThanMs
4049
- })
4050
- if (oldMessages) {
4051
- return originalReadMessages(keys)
4052
- }
4053
- const delay = delayMs()
4054
- return new Promise((resolve, reject) => {
4055
- const timer = setTimeout(async () => {
4056
- pendingTimers.delete(timer)
4057
- try {
4058
- const result = await originalReadMessages(keys)
4059
- resolve(result)
4060
- } catch (err) {
4061
- reject(err)
4062
- }
4063
- }, delay)
4064
- pendingTimers.add(timer)
4065
- })
4066
- }
4067
- return new Proxy(sock, {
4068
- get(target, prop) {
4069
- if (prop === 'readMessages') {
4070
- return wrappedReadMessages
4071
- }
4072
- return target[prop]
4073
- }
4074
- })
4075
- }
4076
- function stop() {
4077
- for (const timer of pendingTimers) {
4078
- clearTimeout(timer)
4079
- }
4080
- pendingTimers.clear()
4081
- }
4082
- return {
4083
- wrap,
4084
- delayMs,
4085
- stop
4086
- }
4087
- }
4088
-
4089
-
4090
- var import_node_module = require('node:module')
4091
- var require2 = (0, import_node_module.createRequire)(__importMetaUrl)
4092
- var NoopLogger = {
4093
- info: () => {},
4094
- warn: () => {},
4095
- error: () => {}
4096
- }
4097
- function proxyRotator(config) {
4098
- const {
4099
- pool,
4100
- strategy = 'round-robin',
4101
- rotateOn = ['disconnect', 'ban-warning'],
4102
- scheduledIntervalMs = 0,
4103
- maxFailures = 3,
4104
- deadCooldownMs = 6e5,
4105
-
4106
- logger = NoopLogger
4107
- } = config
4108
- if (!pool || pool.length === 0) {
4109
- throw new Error('proxyRotator: pool cannot be empty')
4110
- }
4111
- if (pool.length === 1) {
4112
- logger.warn?.('proxyRotator: pool size is 1. Rotation is a no-op.')
4113
- }
4114
- if (scheduledIntervalMs > 0 && scheduledIntervalMs < 6e4) {
4115
- logger.warn?.(`proxyRotator: scheduledIntervalMs (${scheduledIntervalMs}ms) is < 60s. May hammer proxy provider.`)
4116
- }
4117
- const states = pool.map(endpoint => ({
4118
- endpoint,
4119
- failures: 0,
4120
- lastUsedAt: null,
4121
- isDead: false
4122
- }))
4123
- let currentIndex = 0
4124
- let totalRotations = 0
4125
- const rotationsByTrigger = {}
4126
- let scheduledTimer = null
4127
- const agentCache = new Map()
4128
- const moduleCache = {}
4129
- function buildProxyUrl(endpoint) {
4130
- const { type, host, port, username, password } = endpoint
4131
- const auth = username && password ? `${username}:${password}@` : ''
4132
- return `${type}://${auth}${host}:${port}`
4133
- }
4134
- function createAgentForEndpointSync(endpoint) {
4135
- if (agentCache.has(endpoint)) {
4136
- return agentCache.get(endpoint)
4137
- }
4138
- const url = buildProxyUrl(endpoint)
4139
- let agent = null
4140
- try {
4141
- if (endpoint.type === 'socks5' || endpoint.type === 'socks5h') {
4142
- if (!moduleCache['socks-proxy-agent']) {
4143
- try {
4144
- moduleCache['socks-proxy-agent'] = require2('socks-proxy-agent')
4145
- } catch {
4146
- logger.error?.('socks-proxy-agent not installed. Run: npm install socks-proxy-agent')
4147
- return null
4148
- }
4149
- }
4150
- agent = new moduleCache['socks-proxy-agent'].SocksProxyAgent(url)
4151
- } else if (endpoint.type === 'http') {
4152
- if (!moduleCache['http-proxy-agent']) {
4153
- try {
4154
- moduleCache['http-proxy-agent'] = require2('http-proxy-agent')
4155
- } catch {
4156
- logger.error?.('http-proxy-agent not installed. Run: npm install http-proxy-agent')
4157
- return null
4158
- }
4159
- }
4160
- agent = new moduleCache['http-proxy-agent'].HttpProxyAgent(url)
4161
- } else if (endpoint.type === 'https') {
4162
- if (!moduleCache['https-proxy-agent']) {
4163
- try {
4164
- moduleCache['https-proxy-agent'] = require2('https-proxy-agent')
4165
- } catch {
4166
- logger.error?.('https-proxy-agent not installed. Run: npm install https-proxy-agent')
4167
- return null
4168
- }
4169
- }
4170
- agent = new moduleCache['https-proxy-agent'].HttpsProxyAgent(url)
4171
- } else {
4172
- logger.error?.(`Unknown proxy type: ${endpoint.type}`)
4173
- return null
4174
- }
4175
- if (agent) {
4176
- agentCache.set(endpoint, agent)
4177
- }
4178
- return agent
4179
- } catch (err) {
4180
- logger.error?.(`Failed to create agent for ${endpoint.label || endpoint.host}: ${err}`)
4181
- return null
4182
- }
4183
- }
4184
- function getAliveEndpoints() {
4185
- const now = Date.now()
4186
- return states
4187
- .map((s, idx) => {
4188
- if (s.isDead && s.lastUsedAt) {
4189
- if (now - s.lastUsedAt.getTime() >= deadCooldownMs) {
4190
- s.isDead = false
4191
- s.failures = 0
4192
- logger.info?.(`Resurrected endpoint ${s.endpoint.label || s.endpoint.host} after cooldown`)
4193
- }
4194
- }
4195
- const cooldown = s.endpoint.cooldownMs || 0
4196
- if (cooldown > 0 && s.lastUsedAt) {
4197
- if (now - s.lastUsedAt.getTime() < cooldown) {
4198
- return -1
4199
- }
4200
- }
4201
- return !s.isDead ? idx : -1
4202
- })
4203
- .filter(idx => idx !== -1)
4204
- }
4205
- function selectNextIndex(alive) {
4206
- if (alive.length === 0) return currentIndex
4207
- if (strategy === 'round-robin') {
4208
- const afterCurrent = alive.filter(idx => idx > currentIndex)
4209
- if (afterCurrent.length > 0) return afterCurrent[0]
4210
- return alive[0]
4211
- }
4212
- if (strategy === 'random') {
4213
- return alive[Math.floor(Math.random() * alive.length)]
4214
- }
4215
- if (strategy === 'least-recently-used') {
4216
- const neverUsed = alive.filter(idx => states[idx].lastUsedAt === null)
4217
- if (neverUsed.length > 0) {
4218
- return neverUsed[0]
4219
- }
4220
- let oldestIdx = alive[0]
4221
- let oldestTime = states[oldestIdx].lastUsedAt.getTime()
4222
- for (const idx of alive) {
4223
- const time = states[idx].lastUsedAt.getTime()
4224
- if (time < oldestTime) {
4225
- oldestTime = time
4226
- oldestIdx = idx
4227
- }
4228
- }
4229
- return oldestIdx
4230
- }
4231
- if (strategy === 'weighted') {
4232
- const weights = alive.map(idx => {
4233
- const failures = states[idx].failures
4234
- return 1 / (failures + 1)
4235
- })
4236
- const totalWeight = weights.reduce((a, b) => a + b, 0)
4237
- let rand = Math.random() * totalWeight
4238
- for (let i = 0; i < alive.length; i++) {
4239
- rand -= weights[i]
4240
- if (rand <= 0) return alive[i]
4241
- }
4242
- return alive[alive.length - 1]
4243
- }
4244
- return alive[0]
4245
- }
4246
- function rotateImpl(reason = 'manual') {
4247
- if (pool.length === 1) {
4248
- return states[0].endpoint
4249
- }
4250
- const alive = getAliveEndpoints()
4251
- if (alive.length === 0) {
4252
- logger.warn?.('All endpoints are dead. Cannot rotate.')
4253
- return states[currentIndex].endpoint
4254
- }
4255
- const nextIdx = selectNextIndex(alive)
4256
- if (nextIdx === currentIndex && alive.length > 1) {
4257
- const others = alive.filter(idx => idx !== currentIndex)
4258
- if (others.length > 0) {
4259
- currentIndex = others[0]
4260
- } else {
4261
- currentIndex = nextIdx
4262
- }
4263
- } else {
4264
- currentIndex = nextIdx
4265
- }
4266
- states[currentIndex].lastUsedAt = new Date()
4267
- totalRotations++
4268
- rotationsByTrigger[reason] = (rotationsByTrigger[reason] || 0) + 1
4269
- const label = states[currentIndex].endpoint.label || states[currentIndex].endpoint.host
4270
- logger.info?.(`Rotated to endpoint ${label} (reason: ${reason})`)
4271
- return states[currentIndex].endpoint
4272
- }
4273
- function markFailureImpl() {
4274
- const state = states[currentIndex]
4275
- state.failures++
4276
- const label = state.endpoint.label || state.endpoint.host
4277
- logger.warn?.(`Endpoint ${label} failed (${state.failures}/${maxFailures})`)
4278
- if (state.failures >= maxFailures) {
4279
- state.isDead = true
4280
- logger.error?.(`Endpoint ${label} marked DEAD after ${maxFailures} failures`)
4281
- const alive = getAliveEndpoints()
4282
- if (alive.length > 0) {
4283
- rotateImpl('manual')
4284
- }
4285
- }
4286
- }
4287
- function resurrectAllImpl() {
4288
- let count = 0
4289
- for (const state of states) {
4290
- if (state.isDead) {
4291
- state.isDead = false
4292
- state.failures = 0
4293
- count++
4294
- }
4295
- }
4296
- if (count > 0) {
4297
- logger.info?.(`Resurrected ${count} dead endpoint(s)`)
4298
- }
4299
- }
4300
- function stopImpl() {
4301
- if (scheduledTimer) {
4302
- clearInterval(scheduledTimer)
4303
- scheduledTimer = null
4304
- logger.info?.('Stopped scheduled rotation timer')
4305
- }
4306
- }
4307
- function getStatsImpl() {
4308
- return {
4309
- totalRotations,
4310
- rotationsByTrigger: { ...rotationsByTrigger },
4311
- endpointHealth: states.map(s => ({
4312
- label: s.endpoint.label || s.endpoint.host,
4313
- inUse: states[currentIndex] === s,
4314
- failures: s.failures,
4315
- lastUsedAt: s.lastUsedAt,
4316
- isDead: s.isDead
4317
- })),
4318
- currentEndpoint: states[currentIndex].endpoint.label || states[currentIndex].endpoint.host
4319
- }
4320
- }
4321
- function currentAgentImpl() {
4322
- const endpoint = states[currentIndex].endpoint
4323
- return createAgentForEndpointSync(endpoint)
4324
- }
4325
- function currentImpl() {
4326
- return states[currentIndex].endpoint
4327
- }
4328
- if (rotateOn.includes('scheduled') && scheduledIntervalMs > 0) {
4329
- scheduledTimer = setInterval(() => {
4330
- rotateImpl('scheduled')
4331
- }, scheduledIntervalMs)
4332
- logger.info?.(`Scheduled rotation enabled (every ${scheduledIntervalMs}ms)`)
4333
- }
4334
- states[0].lastUsedAt = new Date()
4335
- return {
4336
- currentAgent: currentAgentImpl,
4337
- current: currentImpl,
4338
- rotate: rotateImpl,
4339
- markFailure: markFailureImpl,
4340
- resurrectAll: resurrectAllImpl,
4341
- stop: stopImpl,
4342
- getStats: getStatsImpl
4343
- }
4344
- }
4345
-
4346
- 0 &&
4347
- (module.exports = {
4348
- AntiBan,
4349
- ContactGraphWarmer,
4350
- ContentVariator,
4351
- FileStateAdapter,
4352
- HealthMonitor,
4353
- JidCanonicalizer,
4354
- LidFirstResolver,
4355
- LidResolver,
4356
- MAC_ERROR_CODES,
4357
- MessageQueue,
4358
- MessageRetryReason,
4359
- PRESETS,
4360
- PostReconnectThrottle,
4361
- PresenceChoreographer,
4362
- RateLimiter,
4363
- ReplyRatioGuard,
4364
- RetryReasonTracker,
4365
- Scheduler,
4366
- SessionHealthMonitor,
4367
- StateManager,
4368
- TimelockGuard,
4369
- WarmUp,
4370
- WebhookAlerts,
4371
- applyFingerprint,
4372
- applyGroupMultiplier,
4373
- classifyDisconnect,
4374
- createLidFirstResolver,
4375
- credsSnapshot,
4376
- generateFingerprint,
4377
- getCircadianMultiplier,
4378
- getRetryReasonDescription,
4379
- isBroadcast,
4380
- isGroup,
4381
- isMacError,
4382
- isNewsletter,
4383
- messageRecovery,
4384
- parseRetryReason,
4385
- proxyRotator,
4386
- readReceiptVariance,
4387
- resolveConfig,
4388
- shouldUseGroupProfile,
4389
- wrapSocket,
4390
- wrapWithSessionStability
4391
- })