@ikyyjee/ikyysinggle 1.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/WAProto/GenerateStatics.sh +3 -0
  2. package/WAProto/WAProto.proto +8083 -0
  3. package/WAProto/fix-imports.js +85 -0
  4. package/WAProto/index.d.ts +29095 -0
  5. package/WAProto/index.js +172336 -0
  6. package/engine-requirements.js +10 -0
  7. package/lib/Defaults/index.js +194 -0
  8. package/lib/Signal/Group/ciphertext-message.js +12 -0
  9. package/lib/Signal/Group/group-session-builder.js +30 -0
  10. package/lib/Signal/Group/group_cipher.js +82 -0
  11. package/lib/Signal/Group/index.js +12 -0
  12. package/lib/Signal/Group/keyhelper.js +18 -0
  13. package/lib/Signal/Group/sender-chain-key.js +26 -0
  14. package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
  15. package/lib/Signal/Group/sender-key-message.js +66 -0
  16. package/lib/Signal/Group/sender-key-name.js +48 -0
  17. package/lib/Signal/Group/sender-key-record.js +41 -0
  18. package/lib/Signal/Group/sender-key-state.js +84 -0
  19. package/lib/Signal/Group/sender-message-key.js +26 -0
  20. package/lib/Signal/libsignal.js +431 -0
  21. package/lib/Signal/lid-mapping.js +277 -0
  22. package/lib/Socket/Client/index.js +3 -0
  23. package/lib/Socket/Client/types.js +11 -0
  24. package/lib/Socket/Client/websocket.js +102 -0
  25. package/lib/Socket/aigroups.js +221 -0
  26. package/lib/Socket/business.js +379 -0
  27. package/lib/Socket/chats.js +1193 -0
  28. package/lib/Socket/communities.js +431 -0
  29. package/lib/Socket/graphql.js +524 -0
  30. package/lib/Socket/groups.js +408 -0
  31. package/lib/Socket/index.js +49 -0
  32. package/lib/Socket/interop.js +341 -0
  33. package/lib/Socket/luxu.js +510 -0
  34. package/lib/Socket/managed-account.js +99 -0
  35. package/lib/Socket/messages-recv.js +2009 -0
  36. package/lib/Socket/messages-send.js +1608 -0
  37. package/lib/Socket/mex.js +41 -0
  38. package/lib/Socket/newsletter.js +399 -0
  39. package/lib/Socket/privacy.js +128 -0
  40. package/lib/Socket/registration.js +238 -0
  41. package/lib/Socket/socket.js +1000 -0
  42. package/lib/Socket/text-router.js +67 -0
  43. package/lib/Socket/username.js +234 -0
  44. package/lib/Store/index.js +10 -0
  45. package/lib/Store/keyed-db.js +108 -0
  46. package/lib/Store/make-cache-manager-store.js +85 -0
  47. package/lib/Store/make-in-memory-store.js +198 -0
  48. package/lib/Store/make-ordered-dictionary.js +75 -0
  49. package/lib/Store/object-repository.js +32 -0
  50. package/lib/Types/Auth.js +2 -0
  51. package/lib/Types/Bussines.js +2 -0
  52. package/lib/Types/Call.js +2 -0
  53. package/lib/Types/Chat.js +8 -0
  54. package/lib/Types/Contact.js +2 -0
  55. package/lib/Types/Events.js +2 -0
  56. package/lib/Types/GroupMetadata.js +2 -0
  57. package/lib/Types/Label.js +25 -0
  58. package/lib/Types/LabelAssociation.js +7 -0
  59. package/lib/Types/Message.js +11 -0
  60. package/lib/Types/Mex.js +114 -0
  61. package/lib/Types/Product.js +2 -0
  62. package/lib/Types/Signal.js +2 -0
  63. package/lib/Types/Socket.js +3 -0
  64. package/lib/Types/State.js +56 -0
  65. package/lib/Types/USync.js +2 -0
  66. package/lib/Types/index.js +26 -0
  67. package/lib/Utils/adaptive-healing.js +53 -0
  68. package/lib/Utils/auth-utils.js +302 -0
  69. package/lib/Utils/browser-utils.js +50 -0
  70. package/lib/Utils/business.js +231 -0
  71. package/lib/Utils/chat-utils.js +872 -0
  72. package/lib/Utils/command-loader.js +108 -0
  73. package/lib/Utils/companion-reg-client-utils.js +35 -0
  74. package/lib/Utils/consumer-application.js +106 -0
  75. package/lib/Utils/crypto.js +137 -0
  76. package/lib/Utils/curve25519-js.js +262 -0
  77. package/lib/Utils/decode-wa-message.js +498 -0
  78. package/lib/Utils/event-buffer.js +622 -0
  79. package/lib/Utils/generics.js +403 -0
  80. package/lib/Utils/group-history.js +47 -0
  81. package/lib/Utils/history.js +134 -0
  82. package/lib/Utils/identity-change-handler.js +50 -0
  83. package/lib/Utils/index.js +38 -0
  84. package/lib/Utils/jid-display-normalization.js +198 -0
  85. package/lib/Utils/link-preview.js +85 -0
  86. package/lib/Utils/logger.js +3 -0
  87. package/lib/Utils/lt-hash.js +8 -0
  88. package/lib/Utils/make-mutex.js +33 -0
  89. package/lib/Utils/message-composer.js +273 -0
  90. package/lib/Utils/message-retry-manager.js +267 -0
  91. package/lib/Utils/messages-media.js +791 -0
  92. package/lib/Utils/messages.js +1260 -0
  93. package/lib/Utils/meta-ai-msmsg.js +271 -0
  94. package/lib/Utils/native-bridge.js +77 -0
  95. package/lib/Utils/noise-handler.js +201 -0
  96. package/lib/Utils/offline-node-processor.js +40 -0
  97. package/lib/Utils/optimizer.js +90 -0
  98. package/lib/Utils/pre-key-manager.js +106 -0
  99. package/lib/Utils/process-message.js +630 -0
  100. package/lib/Utils/reporting-utils.js +258 -0
  101. package/lib/Utils/session-pool.js +73 -0
  102. package/lib/Utils/signal.js +207 -0
  103. package/lib/Utils/stanza-ack.js +38 -0
  104. package/lib/Utils/sticker.js +139 -0
  105. package/lib/Utils/sync-action-utils.js +49 -0
  106. package/lib/Utils/tc-token-utils.js +163 -0
  107. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  108. package/lib/Utils/use-sqlite-auth-state.js +168 -0
  109. package/lib/Utils/validate-connection.js +203 -0
  110. package/lib/Utils/view-once-cache.js +79 -0
  111. package/lib/Utils/voip-rekey.js +25 -0
  112. package/lib/Utils/warmup.js +117 -0
  113. package/lib/WABinary/constants.js +1301 -0
  114. package/lib/WABinary/decode.js +262 -0
  115. package/lib/WABinary/encode.js +220 -0
  116. package/lib/WABinary/generic-utils.js +204 -0
  117. package/lib/WABinary/index.js +6 -0
  118. package/lib/WABinary/jid-utils.js +98 -0
  119. package/lib/WABinary/types.js +2 -0
  120. package/lib/WAM/BinaryInfo.js +10 -0
  121. package/lib/WAM/constants.js +22853 -0
  122. package/lib/WAM/encode.js +150 -0
  123. package/lib/WAM/index.js +4 -0
  124. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
  125. package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
  126. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
  127. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  128. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
  129. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
  130. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
  131. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  132. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
  133. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  134. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  135. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  136. package/lib/WAUSync/Protocols/index.js +13 -0
  137. package/lib/WAUSync/USyncQuery.js +127 -0
  138. package/lib/WAUSync/USyncUser.js +31 -0
  139. package/lib/WAUSync/index.js +4 -0
  140. package/lib/antiban.js +4083 -0
  141. package/lib/index.js +24 -0
  142. package/package.json +147 -0
@@ -0,0 +1,203 @@
1
+ import { Boom } from '@hapi/boom';
2
+ import { createHash } from 'crypto';
3
+ import { proto } from '../../WAProto/index.js';
4
+ import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX } from '../Defaults/index.js';
5
+ import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js';
6
+ import { Curve, hmacSign } from './crypto.js';
7
+ import { encodeBigEndian } from './generics.js';
8
+ import { createSignalIdentity } from './signal.js';
9
+ const getUserAgent = (config) => {
10
+ return {
11
+ appVersion: {
12
+ primary: config.version[0],
13
+ secondary: config.version[1],
14
+ tertiary: config.version[2]
15
+ },
16
+ platform: proto.ClientPayload.UserAgent.Platform.MACOS,
17
+ releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
18
+ osVersion: '0.1',
19
+ device: 'Desktop',
20
+ osBuildNumber: '0.1',
21
+ localeLanguageIso6391: 'en',
22
+ mnc: '000',
23
+ mcc: '000',
24
+ localeCountryIso31661Alpha2: config.countryCode
25
+ };
26
+ };
27
+ const PLATFORM_MAP = {
28
+ 'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
29
+ Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
30
+ };
31
+ const getWebInfo = (config) => {
32
+ let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER;
33
+ if (config.syncFullHistory &&
34
+ PLATFORM_MAP[config.browser[0]] &&
35
+ config.browser[1] === 'Desktop') {
36
+ webSubPlatform = PLATFORM_MAP[config.browser[0]];
37
+ }
38
+ return { webSubPlatform };
39
+ };
40
+ const getClientPayload = (config) => {
41
+ const payload = {
42
+ connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
43
+ connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
44
+ userAgent: getUserAgent(config)
45
+ };
46
+ payload.webInfo = getWebInfo(config);
47
+ if (config.pushName) {
48
+ payload.pushName = config.pushName;
49
+ }
50
+ return payload;
51
+ };
52
+ export const generateLoginNode = (userJid, config) => {
53
+ const { user, device } = jidDecode(userJid);
54
+ const payload = {
55
+ ...getClientPayload(config),
56
+ passive: true,
57
+ pull: true,
58
+ username: +user,
59
+ device: device,
60
+ // TODO: investigate (hard set as false atm)
61
+ lidDbMigrated: false
62
+ };
63
+ return proto.ClientPayload.fromObject(payload);
64
+ };
65
+ const getPlatformType = (platform) => {
66
+ const platformType = platform.toUpperCase();
67
+ return (proto.DeviceProps.PlatformType[platformType] ||
68
+ proto.DeviceProps.PlatformType.CHROME);
69
+ };
70
+ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedIdentityKey }, config) => {
71
+ // the app version needs to be md5 hashed
72
+ // and passed in
73
+ const appVersionBuf = createHash('md5')
74
+ .update(config.version.join('.')) // join as string
75
+ .digest();
76
+ const companion = {
77
+ os: config.browser[0],
78
+ platformType: getPlatformType(config.browser[1]),
79
+ requireFullSync: config.syncFullHistory,
80
+ historySyncConfig: {
81
+ storageQuotaMb: 10240,
82
+ inlineInitialPayloadInE2EeMsg: true,
83
+ recentSyncDaysLimit: undefined,
84
+ supportCallLogHistory: false,
85
+ supportBotUserAgentChatHistory: true,
86
+ supportCagReactionsAndPolls: true,
87
+ supportBizHostedMsg: true,
88
+ supportRecentSyncChunkMessageCountTuning: true,
89
+ supportHostedGroupMsg: true,
90
+ supportFbidBotChatHistory: true,
91
+ supportAddOnHistorySyncMigration: undefined,
92
+ supportMessageAssociation: true,
93
+ supportGroupHistory: false,
94
+ onDemandReady: undefined,
95
+ supportGuestChat: undefined
96
+ },
97
+ version: {
98
+ primary: 10,
99
+ secondary: 15,
100
+ tertiary: 7
101
+ }
102
+ };
103
+ const companionProto = proto.DeviceProps.encode(companion).finish();
104
+ const registerPayload = {
105
+ ...getClientPayload(config),
106
+ passive: false,
107
+ pull: false,
108
+ devicePairingData: {
109
+ buildHash: appVersionBuf,
110
+ deviceProps: companionProto,
111
+ eRegid: encodeBigEndian(registrationId),
112
+ eKeytype: KEY_BUNDLE_TYPE,
113
+ eIdent: signedIdentityKey.public,
114
+ eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
115
+ eSkeyVal: signedPreKey.keyPair.public,
116
+ eSkeySig: signedPreKey.signature
117
+ }
118
+ };
119
+ return proto.ClientPayload.fromObject(registerPayload);
120
+ };
121
+ export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
122
+ const msgId = stanza.attrs.id;
123
+ const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success');
124
+ const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity');
125
+ const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform');
126
+ const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device');
127
+ const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz');
128
+ if (!deviceIdentityNode || !deviceNode) {
129
+ throw new Boom('Missing device-identity or device in pair success node', { data: stanza });
130
+ }
131
+ const bizName = businessNode?.attrs.name;
132
+ const jid = deviceNode.attrs.jid;
133
+ const lid = deviceNode.attrs.lid;
134
+ const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content);
135
+ let hmacPrefix = Buffer.from([]);
136
+ if (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED) {
137
+ hmacPrefix = WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX;
138
+ }
139
+ const advSign = hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'));
140
+ if (Buffer.compare(hmac, advSign) !== 0) {
141
+ throw new Boom('Invalid account signature');
142
+ }
143
+ const account = proto.ADVSignedDeviceIdentity.decode(details);
144
+ const { accountSignatureKey, accountSignature, details: deviceDetails } = account;
145
+ const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails);
146
+ const accountSignaturePrefix = deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
147
+ ? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
148
+ : WA_ADV_ACCOUNT_SIG_PREFIX;
149
+ const accountMsg = Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public]);
150
+ if (!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
151
+ throw new Boom('Failed to verify account signature');
152
+ }
153
+ const deviceMsg = Buffer.concat([
154
+ WA_ADV_DEVICE_SIG_PREFIX,
155
+ deviceDetails,
156
+ signedIdentityKey.public,
157
+ accountSignatureKey
158
+ ]);
159
+ account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg);
160
+ const identity = createSignalIdentity(lid, accountSignatureKey);
161
+ const accountEnc = encodeSignedDeviceIdentity(account, false);
162
+ const reply = {
163
+ tag: 'iq',
164
+ attrs: {
165
+ to: S_WHATSAPP_NET,
166
+ type: 'result',
167
+ id: msgId
168
+ },
169
+ content: [
170
+ {
171
+ tag: 'pair-device-sign',
172
+ attrs: {},
173
+ content: [
174
+ {
175
+ tag: 'device-identity',
176
+ attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
177
+ content: accountEnc
178
+ }
179
+ ]
180
+ }
181
+ ]
182
+ };
183
+ const authUpdate = {
184
+ account,
185
+ me: { id: jid, name: bizName, lid },
186
+ signalIdentities: [...(signalIdentities || []), identity],
187
+ platform: platformNode?.attrs.name
188
+ };
189
+ return {
190
+ creds: authUpdate,
191
+ reply
192
+ };
193
+ };
194
+ export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
195
+ account = { ...account };
196
+ // set to null if we are not to include the signature key
197
+ // or if we are including the signature key but it is empty
198
+ if (!includeSignatureKey || !account.accountSignatureKey?.length) {
199
+ account.accountSignatureKey = null;
200
+ }
201
+ return proto.ADVSignedDeviceIdentity.encode(account).finish();
202
+ };
203
+ //# sourceMappingURL=validate-connection.js.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * lib/Utils/view-once-cache.js — opt-in helper that saves view-once media
3
+ * (photos/videos/voice notes that WhatsApp normally lets you open only
4
+ * once) to a local folder as it comes in, before it disappears. You call
5
+ * this explicitly with a folder to use; it does nothing unless you wire it
6
+ * up yourself, and everything stays on your own disk.
7
+ */
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+ import { downloadMediaMessage, extractMessageContent } from './messages.js';
11
+ import { extensionForMediaMessage } from './messages-media.js';
12
+
13
+ const VIEW_ONCE_WRAPPER_KEYS = ['viewOnceMessage', 'viewOnceMessageV2', 'viewOnceMessageV2Extension'];
14
+ const VIEW_ONCE_INNER_KEYS = ['imageMessage', 'videoMessage', 'audioMessage'];
15
+
16
+ export const getViewOnceContent = (message) => {
17
+ if (!message) {
18
+ return null;
19
+ }
20
+ const content = extractMessageContent(message);
21
+ if (!content) {
22
+ return null;
23
+ }
24
+ for (const wrapperKey of VIEW_ONCE_WRAPPER_KEYS) {
25
+ const wrapper = content[wrapperKey];
26
+ if (wrapper?.message) {
27
+ const inner = extractMessageContent(wrapper.message);
28
+ if (inner) {
29
+ return inner;
30
+ }
31
+ }
32
+ }
33
+ for (const key of VIEW_ONCE_INNER_KEYS) {
34
+ if (content[key]?.viewOnce) {
35
+ return { [key]: content[key] };
36
+ }
37
+ }
38
+ return null;
39
+ };
40
+
41
+ /**
42
+ * @param sock A connected socket (needs `sock.ev`, `sock.logger`, `sock.updateMediaMessage`).
43
+ * @param options.cacheDir Folder to save into (default './viewonce-cache').
44
+ * @param options.onCached Optional callback fired after each successful save.
45
+ * @returns a function that unregisters the listener when called.
46
+ */
47
+ export const autoCacheViewOnceMedia = (sock, options = {}) => {
48
+ const cacheDir = options.cacheDir || './viewonce-cache';
49
+ const logger = options.logger || sock.logger;
50
+ if (!fs.existsSync(cacheDir)) {
51
+ fs.mkdirSync(cacheDir, { recursive: true });
52
+ }
53
+ const handler = async ({ messages, type }) => {
54
+ if (type !== 'notify' && type !== 'append') {
55
+ return;
56
+ }
57
+ for (const msg of messages) {
58
+ try {
59
+ const viewOnceContent = getViewOnceContent(msg.message);
60
+ if (!viewOnceContent) {
61
+ continue;
62
+ }
63
+ const mediaType = Object.keys(viewOnceContent)[0];
64
+ const buffer = await downloadMediaMessage(msg, 'buffer', {}, { logger, reuploadRequest: sock.updateMediaMessage });
65
+ const extension = extensionForMediaMessage(viewOnceContent) || 'bin';
66
+ const fileName = `${msg.key.id}.${extension}`;
67
+ const filePath = path.join(cacheDir, fileName);
68
+ fs.writeFileSync(filePath, buffer);
69
+ logger?.debug?.({ id: msg.key.id, filePath, mediaType }, 'cached view-once media');
70
+ options.onCached?.({ id: msg.key.id, jid: msg.key.remoteJid, filePath, type: mediaType });
71
+ }
72
+ catch (error) {
73
+ logger?.warn?.({ error, id: msg.key?.id }, 'failed to cache view-once media');
74
+ }
75
+ }
76
+ };
77
+ sock.ev.on('messages.upsert', handler);
78
+ return () => sock.ev.off('messages.upsert', handler);
79
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * lib/Utils/voip-rekey.js — decode the E2E rekey payload WhatsApp sends to
3
+ * rotate call/media encryption keys during a VOIP call. Pure protobuf
4
+ * decode, no network or crypto side effects of its own.
5
+ */
6
+ import { proto } from '../../WAProto/index.js';
7
+
8
+ const REKEY_TYPE_NAMES = {
9
+ [proto.RekeyKeyType.REKEY_KEY_AUDIO]: 'REKEY_KEY_AUDIO',
10
+ [proto.RekeyKeyType.REKEY_KEY_VIDEO]: 'REKEY_KEY_VIDEO',
11
+ [proto.RekeyKeyType.REKEY_KEY_APPDATA]: 'REKEY_KEY_APPDATA'
12
+ };
13
+
14
+ export const decodeE2eRekeyPayload = (buffer) => {
15
+ if (!Buffer.isBuffer(buffer) && !(buffer instanceof Uint8Array)) {
16
+ throw new TypeError('decodeE2eRekeyPayload: buffer must be Buffer or Uint8Array');
17
+ }
18
+ const decoded = proto.E2eRekeyPayload.decode(buffer);
19
+ return {
20
+ keys: (decoded.keys || []).map((entry) => ({
21
+ type: REKEY_TYPE_NAMES[entry.type] ?? entry.type,
22
+ key: entry.key ? Buffer.from(entry.key) : Buffer.alloc(0)
23
+ }))
24
+ };
25
+ };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * lib/Utils/warmup.js — "AntiBanned" fresh-number throttle
3
+ *
4
+ * What this is: a small, transparent daily-send-limit ramp for numbers that
5
+ * were only recently connected to this socket. Brand new WhatsApp numbers
6
+ * get flagged for review more easily if they suddenly send a lot of
7
+ * messages, so this throttle starts them at a low daily cap and increases
8
+ * it every day until it "graduates" to unlimited after `warmUpDays`.
9
+ *
10
+ * What this is NOT: this does not touch message content, timing jitter
11
+ * beyond a plain send delay, device/browser fingerprints, proxies, or
12
+ * anything that tries to make automated traffic look indistinguishable
13
+ * from a human. It only answers one question — "has this number sent a
14
+ * suspiciously large number of messages today for how new it is?" — and
15
+ * either delays or blocks the send if so. See README.md → "AntiBanned".
16
+ *
17
+ * "Fresh" vs "old" is decided purely from state you keep on your own end
18
+ * (see `existingState` below) — this module makes no WhatsApp API calls
19
+ * and reads no account data; it's just a local counter.
20
+ */
21
+
22
+ export const DEFAULT_WARMUP_CONFIG = {
23
+ /** How many days of ramp-up before this number is treated as "old" (unlimited). */
24
+ warmUpDays: 7,
25
+ /** Max messages allowed on day 1. */
26
+ day1Limit: 20,
27
+ /** Daily limit is multiplied by this factor each subsequent day. */
28
+ growthFactor: 1.8,
29
+ /** If graduated but silent for this many hours, restart the ramp from day 1. */
30
+ inactivityThresholdHours: 24 * 3
31
+ };
32
+
33
+ export class NumberWarmUp {
34
+ /**
35
+ * @param config Partial<typeof DEFAULT_WARMUP_CONFIG>
36
+ * @param existingState Pass in `exportState()` from a previous run (e.g.
37
+ * loaded alongside your auth state) so the ramp survives restarts.
38
+ * Omit it and every process restart is treated as a fresh number.
39
+ */
40
+ constructor(config = {}, existingState) {
41
+ this.config = { ...DEFAULT_WARMUP_CONFIG, ...config };
42
+ this.state = existingState || this.freshState();
43
+ }
44
+
45
+ freshState() {
46
+ const now = Date.now();
47
+ return {
48
+ startedAt: now,
49
+ lastActiveAt: now,
50
+ dailyCounts: [],
51
+ graduated: false
52
+ };
53
+ }
54
+
55
+ getCurrentDay() {
56
+ return Math.floor((Date.now() - this.state.startedAt) / (24 * 60 * 60 * 1000));
57
+ }
58
+
59
+ checkInactivity() {
60
+ const hoursSinceActive = (Date.now() - this.state.lastActiveAt) / (60 * 60 * 1000);
61
+ if (this.state.graduated && hoursSinceActive > this.config.inactivityThresholdHours) {
62
+ this.state = this.freshState();
63
+ }
64
+ }
65
+
66
+ getDailyLimit() {
67
+ if (this.state.graduated) {
68
+ return Infinity;
69
+ }
70
+ const day = this.getCurrentDay();
71
+ if (day >= this.config.warmUpDays) {
72
+ this.state.graduated = true;
73
+ return Infinity;
74
+ }
75
+ return Math.round(this.config.day1Limit * Math.pow(this.config.growthFactor, day));
76
+ }
77
+
78
+ /** true = "old enough" or under today's limit; false = should be delayed/blocked. */
79
+ canSend() {
80
+ this.checkInactivity();
81
+ if (this.state.graduated) {
82
+ return true;
83
+ }
84
+ const day = this.getCurrentDay();
85
+ const sentToday = this.state.dailyCounts[day] || 0;
86
+ return sentToday < this.getDailyLimit();
87
+ }
88
+
89
+ /** Call once per message actually sent. */
90
+ record() {
91
+ const day = this.getCurrentDay();
92
+ while (this.state.dailyCounts.length <= day) {
93
+ this.state.dailyCounts.push(0);
94
+ }
95
+ this.state.dailyCounts[day]++;
96
+ this.state.lastActiveAt = Date.now();
97
+ }
98
+
99
+ getStatus() {
100
+ this.checkInactivity();
101
+ const day = this.getCurrentDay();
102
+ const sentToday = this.state.dailyCounts[day] || 0;
103
+ const limit = this.getDailyLimit();
104
+ return {
105
+ isFreshNumber: !this.state.graduated,
106
+ day: Math.min(day + 1, this.config.warmUpDays),
107
+ totalWarmUpDays: this.config.warmUpDays,
108
+ todayLimit: limit === Infinity ? null : limit,
109
+ todaySent: sentToday
110
+ };
111
+ }
112
+
113
+ /** Persist alongside your auth state so the ramp survives restarts. */
114
+ exportState() {
115
+ return { ...this.state, dailyCounts: [...this.state.dailyCounts] };
116
+ }
117
+ }