@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.
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +8083 -0
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +29095 -0
- package/WAProto/index.js +172336 -0
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.js +194 -0
- package/lib/Signal/Group/ciphertext-message.js +12 -0
- package/lib/Signal/Group/group-session-builder.js +30 -0
- package/lib/Signal/Group/group_cipher.js +82 -0
- package/lib/Signal/Group/index.js +12 -0
- package/lib/Signal/Group/keyhelper.js +18 -0
- package/lib/Signal/Group/sender-chain-key.js +26 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
- package/lib/Signal/Group/sender-key-message.js +66 -0
- package/lib/Signal/Group/sender-key-name.js +48 -0
- package/lib/Signal/Group/sender-key-record.js +41 -0
- package/lib/Signal/Group/sender-key-state.js +84 -0
- package/lib/Signal/Group/sender-message-key.js +26 -0
- package/lib/Signal/libsignal.js +431 -0
- package/lib/Signal/lid-mapping.js +277 -0
- package/lib/Socket/Client/index.js +3 -0
- package/lib/Socket/Client/types.js +11 -0
- package/lib/Socket/Client/websocket.js +102 -0
- package/lib/Socket/aigroups.js +221 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.js +1193 -0
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/graphql.js +524 -0
- package/lib/Socket/groups.js +408 -0
- package/lib/Socket/index.js +49 -0
- package/lib/Socket/interop.js +341 -0
- package/lib/Socket/luxu.js +510 -0
- package/lib/Socket/managed-account.js +99 -0
- package/lib/Socket/messages-recv.js +2009 -0
- package/lib/Socket/messages-send.js +1608 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.js +399 -0
- package/lib/Socket/privacy.js +128 -0
- package/lib/Socket/registration.js +238 -0
- package/lib/Socket/socket.js +1000 -0
- package/lib/Socket/text-router.js +67 -0
- package/lib/Socket/username.js +234 -0
- package/lib/Store/index.js +10 -0
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.js +85 -0
- package/lib/Store/make-in-memory-store.js +198 -0
- package/lib/Store/make-ordered-dictionary.js +75 -0
- package/lib/Store/object-repository.js +32 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +8 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +25 -0
- package/lib/Types/LabelAssociation.js +7 -0
- package/lib/Types/Message.js +11 -0
- package/lib/Types/Mex.js +114 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +3 -0
- package/lib/Types/State.js +56 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +26 -0
- package/lib/Utils/adaptive-healing.js +53 -0
- package/lib/Utils/auth-utils.js +302 -0
- package/lib/Utils/browser-utils.js +50 -0
- package/lib/Utils/business.js +231 -0
- package/lib/Utils/chat-utils.js +872 -0
- package/lib/Utils/command-loader.js +108 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/consumer-application.js +106 -0
- package/lib/Utils/crypto.js +137 -0
- package/lib/Utils/curve25519-js.js +262 -0
- package/lib/Utils/decode-wa-message.js +498 -0
- package/lib/Utils/event-buffer.js +622 -0
- package/lib/Utils/generics.js +403 -0
- package/lib/Utils/group-history.js +47 -0
- package/lib/Utils/history.js +134 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +38 -0
- package/lib/Utils/jid-display-normalization.js +198 -0
- package/lib/Utils/link-preview.js +85 -0
- package/lib/Utils/logger.js +3 -0
- package/lib/Utils/lt-hash.js +8 -0
- package/lib/Utils/make-mutex.js +33 -0
- package/lib/Utils/message-composer.js +273 -0
- package/lib/Utils/message-retry-manager.js +267 -0
- package/lib/Utils/messages-media.js +791 -0
- package/lib/Utils/messages.js +1260 -0
- package/lib/Utils/meta-ai-msmsg.js +271 -0
- package/lib/Utils/native-bridge.js +77 -0
- package/lib/Utils/noise-handler.js +201 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/optimizer.js +90 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/process-message.js +630 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/session-pool.js +73 -0
- package/lib/Utils/signal.js +207 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sticker.js +139 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/Utils/use-multi-file-auth-state.js +121 -0
- package/lib/Utils/use-sqlite-auth-state.js +168 -0
- package/lib/Utils/validate-connection.js +203 -0
- package/lib/Utils/view-once-cache.js +79 -0
- package/lib/Utils/voip-rekey.js +25 -0
- package/lib/Utils/warmup.js +117 -0
- package/lib/WABinary/constants.js +1301 -0
- package/lib/WABinary/decode.js +262 -0
- package/lib/WABinary/encode.js +220 -0
- package/lib/WABinary/generic-utils.js +204 -0
- package/lib/WABinary/index.js +6 -0
- package/lib/WABinary/jid-utils.js +98 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.js +10 -0
- package/lib/WAM/constants.js +22853 -0
- package/lib/WAM/encode.js +150 -0
- package/lib/WAM/index.js +4 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
- package/lib/WAUSync/Protocols/index.js +13 -0
- package/lib/WAUSync/USyncQuery.js +127 -0
- package/lib/WAUSync/USyncUser.js +31 -0
- package/lib/WAUSync/index.js +4 -0
- package/lib/antiban.js +4083 -0
- package/lib/index.js +24 -0
- 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
|
+
}
|