@codexverified/baileys 2.7.5
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/LICENSE +21 -0
- package/README.md +233 -0
- package/WAProto/V +1 -0
- package/WAProto/WAProto.proto +8051 -0
- package/WAProto/index.js +289292 -0
- package/engine-requirements.js +13 -0
- package/lib/Defaults/index.js +158 -0
- package/lib/Signal/Group/ciphertext-message.js +11 -0
- package/lib/Signal/Group/group-session-builder.js +29 -0
- package/lib/Signal/Group/group_cipher.js +81 -0
- package/lib/Signal/Group/index.js +11 -0
- package/lib/Signal/Group/keyhelper.js +17 -0
- package/lib/Signal/Group/sender-chain-key.js +25 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
- package/lib/Signal/Group/sender-key-message.js +65 -0
- package/lib/Signal/Group/sender-key-name.js +47 -0
- package/lib/Signal/Group/sender-key-record.js +40 -0
- package/lib/Signal/Group/sender-key-state.js +83 -0
- package/lib/Signal/Group/sender-message-key.js +25 -0
- package/lib/Signal/libsignal.js +428 -0
- package/lib/Signal/lid-mapping.js +280 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/aigroups.js +315 -0
- package/lib/Socket/business.js +407 -0
- package/lib/Socket/chats.js +1831 -0
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/graphql.js +899 -0
- package/lib/Socket/groups.js +549 -0
- package/lib/Socket/index.js +79 -0
- package/lib/Socket/interactive-handler.js +600 -0
- package/lib/Socket/interop.js +563 -0
- package/lib/Socket/managed-account.js +208 -0
- package/lib/Socket/messages-recv.js +2144 -0
- package/lib/Socket/messages-send.js +2030 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.js +226 -0
- package/lib/Socket/privacy.js +446 -0
- package/lib/Socket/registration.js +428 -0
- package/lib/Socket/socket.js +1017 -0
- package/lib/Socket/username.js +229 -0
- package/lib/Store/index.js +4 -0
- package/lib/Store/make-cache-manager-store.js +89 -0
- package/lib/Store/make-in-memory-store.js +420 -0
- package/lib/Store/make-ordered-dictionary.js +78 -0
- package/lib/Store/object-repository.js +23 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.js +1 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.js +17 -0
- package/lib/Types/Mex.js +39 -0
- package/lib/Types/Product.js +1 -0
- package/lib/Types/RichType.js +22 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.js +48 -0
- package/lib/Types/USync.js +1 -0
- package/lib/Types/index.js +25 -0
- package/lib/Utils/auth-utils.js +301 -0
- package/lib/Utils/bot-planning-replay.js +169 -0
- package/lib/Utils/browser-utils.js +28 -0
- package/lib/Utils/business.js +230 -0
- package/lib/Utils/chat-utils.js +885 -0
- package/lib/Utils/companion-reg-client-utils.js +32 -0
- package/lib/Utils/consumer-application.js +102 -0
- package/lib/Utils/crypto.js +117 -0
- package/lib/Utils/decode-wa-message.js +484 -0
- package/lib/Utils/event-buffer.js +589 -0
- package/lib/Utils/generics.js +388 -0
- package/lib/Utils/group-history.js +56 -0
- package/lib/Utils/history.js +130 -0
- package/lib/Utils/identity-change-handler.js +48 -0
- package/lib/Utils/index.js +34 -0
- package/lib/Utils/jid-display-normalization.js +209 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.js +7 -0
- package/lib/Utils/make-mutex.js +32 -0
- package/lib/Utils/message-composer.js +467 -0
- package/lib/Utils/message-inspect.js +67 -0
- package/lib/Utils/message-retry-manager.js +241 -0
- package/lib/Utils/messages-media.js +1006 -0
- package/lib/Utils/messages.js +1851 -0
- package/lib/Utils/meta-ai-msmsg.js +130 -0
- package/lib/Utils/meta-compositing.js +194 -0
- package/lib/Utils/noise-handler.js +200 -0
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.js +527 -0
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/rich-message-utils.js +439 -0
- package/lib/Utils/signal.js +200 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/Utils/sync-action-utils.js +47 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/use-multi-file-auth-state.js +120 -0
- package/lib/Utils/use-single-file-auth-state.js +96 -0
- package/lib/Utils/use-sqlite-auth-state.js +108 -0
- package/lib/Utils/validate-connection.js +206 -0
- package/lib/Utils/voip-rekey.js +20 -0
- package/lib/Utils/welcome-flow.js +217 -0
- package/lib/WABinary/constants.js +1463 -0
- package/lib/WABinary/decode.js +261 -0
- package/lib/WABinary/encode.js +219 -0
- package/lib/WABinary/generic-utils.js +227 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.js +95 -0
- package/lib/WABinary/types.js +1 -0
- package/lib/WAM/BinaryInfo.js +9 -0
- package/lib/WAM/constants.js +22852 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +96 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +56 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +70 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +25 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +34 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
- package/lib/WAUSync/Protocols/index.js +12 -0
- package/lib/WAUSync/USyncQuery.js +201 -0
- package/lib/WAUSync/USyncUser.js +54 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/antiban.cjs +4726 -0
- package/lib/index.d.ts +286 -0
- package/lib/index.js +18 -0
- package/package.json +121 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { proto } from '../../WAProto/index.js';
|
|
4
|
+
import { initAuthCreds } from './auth-utils.js';
|
|
5
|
+
import { BufferJSON } from './generics.js';
|
|
6
|
+
async function loadBetterSqlite3() {
|
|
7
|
+
try {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
const mod = (await import('better-sqlite3'));
|
|
10
|
+
return mod.default ?? mod;
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
const helpful = new Error('`better-sqlite3` is required for `useSqliteAuthState`. Install it as a peer dependency: `npm install better-sqlite3` (or `yarn add better-sqlite3`).');
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
helpful.cause = err;
|
|
16
|
+
throw helpful;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const CREDS_ROW_KEY = '__creds__';
|
|
20
|
+
const CREATE_SCHEMA_SQL = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS creds (
|
|
22
|
+
key TEXT PRIMARY KEY,
|
|
23
|
+
value TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
CREATE TABLE IF NOT EXISTS signal_keys (
|
|
26
|
+
type TEXT NOT NULL,
|
|
27
|
+
id TEXT NOT NULL,
|
|
28
|
+
value TEXT NOT NULL,
|
|
29
|
+
PRIMARY KEY (type, id)
|
|
30
|
+
);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS signal_keys_type_idx ON signal_keys(type);
|
|
32
|
+
`;
|
|
33
|
+
export async function useSqliteAuthState(opts) {
|
|
34
|
+
let db;
|
|
35
|
+
if (opts.database) {
|
|
36
|
+
db = opts.database;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const Database = await loadBetterSqlite3();
|
|
40
|
+
db = new Database(opts.dbPath);
|
|
41
|
+
}
|
|
42
|
+
// WAL mode allows concurrent reads alongside a single writer; matches
|
|
43
|
+
// what SQLite recommends for read-heavy workloads with sporadic writes.
|
|
44
|
+
db.pragma('journal_mode = WAL');
|
|
45
|
+
db.pragma('synchronous = NORMAL');
|
|
46
|
+
db.exec(CREATE_SCHEMA_SQL);
|
|
47
|
+
const stmts = {
|
|
48
|
+
credsSelect: db.prepare('SELECT value FROM creds WHERE key = ?'),
|
|
49
|
+
credsUpsert: db.prepare('INSERT INTO creds (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'),
|
|
50
|
+
keySelect: db.prepare('SELECT value FROM signal_keys WHERE type = ? AND id = ?'),
|
|
51
|
+
keyUpsert: db.prepare('INSERT INTO signal_keys (type, id, value) VALUES (?, ?, ?) ON CONFLICT(type, id) DO UPDATE SET value = excluded.value'),
|
|
52
|
+
keyDelete: db.prepare('DELETE FROM signal_keys WHERE type = ? AND id = ?'),
|
|
53
|
+
keyListIds: db.prepare('SELECT id FROM signal_keys WHERE type = ?'),
|
|
54
|
+
keyList: db.prepare('SELECT id, value FROM signal_keys WHERE type = ?'),
|
|
55
|
+
clearKeys: db.prepare('DELETE FROM signal_keys')
|
|
56
|
+
};
|
|
57
|
+
const loadCreds = () => {
|
|
58
|
+
const row = stmts.credsSelect.get(CREDS_ROW_KEY);
|
|
59
|
+
if (!row)
|
|
60
|
+
return initAuthCreds();
|
|
61
|
+
return JSON.parse(row.value, BufferJSON.reviver);
|
|
62
|
+
};
|
|
63
|
+
const persistCreds = (creds) => {
|
|
64
|
+
stmts.credsUpsert.run(CREDS_ROW_KEY, JSON.stringify(creds, BufferJSON.replacer));
|
|
65
|
+
};
|
|
66
|
+
const creds = loadCreds();
|
|
67
|
+
return {
|
|
68
|
+
state: {
|
|
69
|
+
creds,
|
|
70
|
+
keys: {
|
|
71
|
+
get: async (type, ids) => {
|
|
72
|
+
const data = {};
|
|
73
|
+
for (const id of ids) {
|
|
74
|
+
const row = stmts.keySelect.get(type, id);
|
|
75
|
+
if (row) {
|
|
76
|
+
let value = JSON.parse(row.value, BufferJSON.reviver);
|
|
77
|
+
if (type === 'app-state-sync-key' && value) {
|
|
78
|
+
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
|
79
|
+
}
|
|
80
|
+
data[id] = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
},
|
|
85
|
+
set: async (data) => {
|
|
86
|
+
const writeTx = db.transaction(() => {
|
|
87
|
+
for (const category in data) {
|
|
88
|
+
for (const id in data[category]) {
|
|
89
|
+
const value = data[category][id];
|
|
90
|
+
if (value) {
|
|
91
|
+
const stringified = JSON.stringify(value, BufferJSON.replacer);
|
|
92
|
+
stmts.keyUpsert.run(category, id, stringified);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
stmts.keyDelete.run(category, id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
writeTx();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
saveCreds: async () => {
|
|
105
|
+
persistCreds(creds);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
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: config.browser[1].toLocaleLowerCase().includes('android')
|
|
17
|
+
? proto.ClientPayload.UserAgent.Platform.ANDROID
|
|
18
|
+
: proto.ClientPayload.UserAgent.Platform.WEB,
|
|
19
|
+
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
|
20
|
+
osVersion: '0.1',
|
|
21
|
+
device: 'Desktop',
|
|
22
|
+
osBuildNumber: '0.1',
|
|
23
|
+
localeLanguageIso6391: 'en',
|
|
24
|
+
mnc: '000',
|
|
25
|
+
mcc: '000',
|
|
26
|
+
localeCountryIso31661Alpha2: config.countryCode
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const PLATFORM_MAP = {
|
|
30
|
+
'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
|
|
31
|
+
Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
|
|
32
|
+
};
|
|
33
|
+
const getWebInfo = (config) => {
|
|
34
|
+
let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER;
|
|
35
|
+
if (config.syncFullHistory &&
|
|
36
|
+
PLATFORM_MAP[config.browser[0]] &&
|
|
37
|
+
config.browser[1] === 'Desktop') {
|
|
38
|
+
webSubPlatform = PLATFORM_MAP[config.browser[0]];
|
|
39
|
+
}
|
|
40
|
+
return { webSubPlatform };
|
|
41
|
+
};
|
|
42
|
+
const getClientPayload = (config) => {
|
|
43
|
+
const payload = {
|
|
44
|
+
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
|
45
|
+
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
|
46
|
+
userAgent: getUserAgent(config)
|
|
47
|
+
};
|
|
48
|
+
if (!config.browser[1].toLocaleLowerCase().includes('android')) {
|
|
49
|
+
payload.webInfo = getWebInfo(config);
|
|
50
|
+
}
|
|
51
|
+
return payload;
|
|
52
|
+
};
|
|
53
|
+
export const generateLoginNode = (userJid, config) => {
|
|
54
|
+
const { user, device } = jidDecode(userJid);
|
|
55
|
+
const payload = {
|
|
56
|
+
...getClientPayload(config),
|
|
57
|
+
passive: true,
|
|
58
|
+
pull: true,
|
|
59
|
+
username: +user,
|
|
60
|
+
device: device,
|
|
61
|
+
// TODO: investigate (hard set as false atm)
|
|
62
|
+
lidDbMigrated: false
|
|
63
|
+
};
|
|
64
|
+
return proto.ClientPayload.fromObject(payload);
|
|
65
|
+
};
|
|
66
|
+
const getPlatformType = (platform) => {
|
|
67
|
+
const platformType = platform.toUpperCase();
|
|
68
|
+
if (platformType === 'ANDROID') {
|
|
69
|
+
return proto.DeviceProps.PlatformType.ANDROID_PHONE;
|
|
70
|
+
}
|
|
71
|
+
return (proto.DeviceProps.PlatformType[platformType] ||
|
|
72
|
+
proto.DeviceProps.PlatformType.CHROME);
|
|
73
|
+
};
|
|
74
|
+
export const generateRegistrationNode = ({ registrationId, signedPreKey, signedIdentityKey }, config) => {
|
|
75
|
+
// the app version needs to be md5 hashed
|
|
76
|
+
// and passed in
|
|
77
|
+
const appVersionBuf = createHash('md5')
|
|
78
|
+
.update(config.version.join('.')) // join as string
|
|
79
|
+
.digest();
|
|
80
|
+
const companion = {
|
|
81
|
+
os: config.browser[0],
|
|
82
|
+
platformType: getPlatformType(config.browser[1]),
|
|
83
|
+
requireFullSync: config.syncFullHistory,
|
|
84
|
+
historySyncConfig: {
|
|
85
|
+
storageQuotaMb: 10240,
|
|
86
|
+
inlineInitialPayloadInE2EeMsg: true,
|
|
87
|
+
recentSyncDaysLimit: undefined,
|
|
88
|
+
supportCallLogHistory: false,
|
|
89
|
+
supportBotUserAgentChatHistory: true,
|
|
90
|
+
supportCagReactionsAndPolls: true,
|
|
91
|
+
supportBizHostedMsg: true,
|
|
92
|
+
supportRecentSyncChunkMessageCountTuning: true,
|
|
93
|
+
supportHostedGroupMsg: true,
|
|
94
|
+
supportFbidBotChatHistory: true,
|
|
95
|
+
supportAddOnHistorySyncMigration: undefined,
|
|
96
|
+
supportMessageAssociation: true,
|
|
97
|
+
supportGroupHistory: false,
|
|
98
|
+
onDemandReady: undefined,
|
|
99
|
+
supportGuestChat: undefined
|
|
100
|
+
},
|
|
101
|
+
version: {
|
|
102
|
+
primary: 10,
|
|
103
|
+
secondary: 15,
|
|
104
|
+
tertiary: 7
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const companionProto = proto.DeviceProps.encode(companion).finish();
|
|
108
|
+
const registerPayload = {
|
|
109
|
+
...getClientPayload(config),
|
|
110
|
+
passive: false,
|
|
111
|
+
pull: false,
|
|
112
|
+
devicePairingData: {
|
|
113
|
+
buildHash: appVersionBuf,
|
|
114
|
+
deviceProps: companionProto,
|
|
115
|
+
eRegid: encodeBigEndian(registrationId),
|
|
116
|
+
eKeytype: KEY_BUNDLE_TYPE,
|
|
117
|
+
eIdent: signedIdentityKey.public,
|
|
118
|
+
eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
|
|
119
|
+
eSkeyVal: signedPreKey.keyPair.public,
|
|
120
|
+
eSkeySig: signedPreKey.signature
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
return proto.ClientPayload.fromObject(registerPayload);
|
|
124
|
+
};
|
|
125
|
+
export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
|
|
126
|
+
const msgId = stanza.attrs.id;
|
|
127
|
+
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success');
|
|
128
|
+
const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity');
|
|
129
|
+
const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform');
|
|
130
|
+
const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device');
|
|
131
|
+
const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz');
|
|
132
|
+
if (!deviceIdentityNode || !deviceNode) {
|
|
133
|
+
throw new Boom('Missing device-identity or device in pair success node', { data: stanza });
|
|
134
|
+
}
|
|
135
|
+
const bizName = businessNode?.attrs.name;
|
|
136
|
+
const jid = deviceNode.attrs.jid;
|
|
137
|
+
const lid = deviceNode.attrs.lid;
|
|
138
|
+
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content);
|
|
139
|
+
let hmacPrefix = Buffer.from([]);
|
|
140
|
+
if (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED) {
|
|
141
|
+
hmacPrefix = WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX;
|
|
142
|
+
}
|
|
143
|
+
const advSign = hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'));
|
|
144
|
+
if (Buffer.compare(hmac, advSign) !== 0) {
|
|
145
|
+
throw new Boom('Invalid account signature');
|
|
146
|
+
}
|
|
147
|
+
const account = proto.ADVSignedDeviceIdentity.decode(details);
|
|
148
|
+
const { accountSignatureKey, accountSignature, details: deviceDetails } = account;
|
|
149
|
+
const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails);
|
|
150
|
+
const accountSignaturePrefix = deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
|
|
151
|
+
? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
|
152
|
+
: WA_ADV_ACCOUNT_SIG_PREFIX;
|
|
153
|
+
const accountMsg = Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public]);
|
|
154
|
+
if (!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
|
|
155
|
+
throw new Boom('Failed to verify account signature');
|
|
156
|
+
}
|
|
157
|
+
const deviceMsg = Buffer.concat([
|
|
158
|
+
WA_ADV_DEVICE_SIG_PREFIX,
|
|
159
|
+
deviceDetails,
|
|
160
|
+
signedIdentityKey.public,
|
|
161
|
+
accountSignatureKey
|
|
162
|
+
]);
|
|
163
|
+
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg);
|
|
164
|
+
const identity = createSignalIdentity(lid, accountSignatureKey);
|
|
165
|
+
const accountEnc = encodeSignedDeviceIdentity(account, false);
|
|
166
|
+
const reply = {
|
|
167
|
+
tag: 'iq',
|
|
168
|
+
attrs: {
|
|
169
|
+
to: S_WHATSAPP_NET,
|
|
170
|
+
type: 'result',
|
|
171
|
+
id: msgId
|
|
172
|
+
},
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
tag: 'pair-device-sign',
|
|
176
|
+
attrs: {},
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
tag: 'device-identity',
|
|
180
|
+
attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
|
|
181
|
+
content: accountEnc
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
const authUpdate = {
|
|
188
|
+
account,
|
|
189
|
+
me: { id: jid, name: bizName, lid },
|
|
190
|
+
signalIdentities: [...(signalIdentities || []), identity],
|
|
191
|
+
platform: platformNode?.attrs.name
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
creds: authUpdate,
|
|
195
|
+
reply
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
|
|
199
|
+
account = { ...account };
|
|
200
|
+
// set to null if we are not to include the signature key
|
|
201
|
+
// or if we are including the signature key but it is empty
|
|
202
|
+
if (!includeSignatureKey || !account.accountSignatureKey?.length) {
|
|
203
|
+
account.accountSignatureKey = null;
|
|
204
|
+
}
|
|
205
|
+
return proto.ADVSignedDeviceIdentity.encode(account).finish();
|
|
206
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { proto } from '../../WAProto/index.js';
|
|
2
|
+
|
|
3
|
+
const REKEY_TYPE_NAMES = {
|
|
4
|
+
[proto.RekeyKeyType.REKEY_KEY_AUDIO]: 'REKEY_KEY_AUDIO',
|
|
5
|
+
[proto.RekeyKeyType.REKEY_KEY_VIDEO]: 'REKEY_KEY_VIDEO',
|
|
6
|
+
[proto.RekeyKeyType.REKEY_KEY_APPDATA]: 'REKEY_KEY_APPDATA'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const decodeE2eRekeyPayload = (buffer) => {
|
|
10
|
+
if (!Buffer.isBuffer(buffer) && !(buffer instanceof Uint8Array)) {
|
|
11
|
+
throw new TypeError('decodeE2eRekeyPayload: buffer must be Buffer or Uint8Array');
|
|
12
|
+
}
|
|
13
|
+
const decoded = proto.E2eRekeyPayload.decode(buffer);
|
|
14
|
+
return {
|
|
15
|
+
keys: (decoded.keys || []).map(entry => ({
|
|
16
|
+
type: REKEY_TYPE_NAMES[entry.type] ?? entry.type,
|
|
17
|
+
key: entry.key ? Buffer.from(entry.key) : Buffer.alloc(0)
|
|
18
|
+
}))
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lia@Changes [WIP]
|
|
3
|
+
* Welcome Flow Meta Business-style startup template.
|
|
4
|
+
*
|
|
5
|
+
* Fires automatically on first message from any new contact.
|
|
6
|
+
* Sends a fully configurable greeting with interactive FAQ buttons,
|
|
7
|
+
* exactly like Meta Business account welcome templates.
|
|
8
|
+
*
|
|
9
|
+
* Tracks seen JIDs in memory + optional persistent JSON file so the
|
|
10
|
+
* greeting fires ONCE per contact never on repeat messages.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const welcome = createWelcomeFlow(sock, config)
|
|
14
|
+
* welcome.listen() // start listening
|
|
15
|
+
* welcome.stop() // stop listening
|
|
16
|
+
* welcome.reset(jid) // force re-greet a contact
|
|
17
|
+
* welcome.resetAll() // clear all seen contacts
|
|
18
|
+
*
|
|
19
|
+
* If you use or copy this code, please credit @codexverified/baileys.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
23
|
+
import { isJidGroup, isJidNewsletter, isJidBroadcast, jidNormalizedUser } from '../WABinary/index.js';
|
|
24
|
+
import { delay } from './generics.js';
|
|
25
|
+
|
|
26
|
+
// Defaults
|
|
27
|
+
|
|
28
|
+
const DEFAULT_CONFIG = {
|
|
29
|
+
greeting: ' Welcome! How can I help you today?',
|
|
30
|
+
footer: 'Powered by @codexverified/baileys',
|
|
31
|
+
buttonText: ' Choose an option',
|
|
32
|
+
faqs: [
|
|
33
|
+
{ id: 'faq_1', title: ' Track my order', description: 'Check order status' },
|
|
34
|
+
{ id: 'faq_2', title: ' Billing & payments', description: 'Payment issues & invoices' },
|
|
35
|
+
{ id: 'faq_3', title: ' Technical support', description: 'Get help with a problem' },
|
|
36
|
+
{ id: 'faq_4', title: ' Talk to a human', description: 'Connect with support staff' },
|
|
37
|
+
],
|
|
38
|
+
sectionTitle: 'How can we help?',
|
|
39
|
+
typingDelayMs: 1200, // simulate typing before greeting appears
|
|
40
|
+
persistPath: null, // set to a file path to persist seen JIDs across restarts
|
|
41
|
+
ignoreGroups: true, // don't fire in group chats
|
|
42
|
+
ignoreNewsletter: true, // don't fire for newsletter messages
|
|
43
|
+
ignoreBroadcast: true, // don't fire for broadcast messages
|
|
44
|
+
onGreet: null, // async (jid, message) => {} called after greeting sent
|
|
45
|
+
onFaqReply: null, // async (jid, faqId, message) => {} called when user picks a FAQ
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Persistent seen-JID store
|
|
49
|
+
|
|
50
|
+
const createSeenStore = (persistPath) => {
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
|
|
53
|
+
// Load from file if path given and file exists
|
|
54
|
+
if (persistPath && existsSync(persistPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const data = JSON.parse(readFileSync(persistPath, 'utf8'));
|
|
57
|
+
if (Array.isArray(data)) data.forEach(jid => seen.add(jid));
|
|
58
|
+
} catch (_) {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const save = () => {
|
|
62
|
+
if (!persistPath) return;
|
|
63
|
+
try {
|
|
64
|
+
writeFileSync(persistPath, JSON.stringify([...seen]), 'utf8');
|
|
65
|
+
} catch (_) {}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
has: (jid) => seen.has(jid),
|
|
70
|
+
add: (jid) => { seen.add(jid); save(); },
|
|
71
|
+
delete: (jid) => { seen.delete(jid); save(); },
|
|
72
|
+
clear: () => { seen.clear(); save(); }
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Build the welcome interactive list message
|
|
77
|
+
|
|
78
|
+
const buildWelcomeMessage = (config, quotedMsg = null) => {
|
|
79
|
+
const { greeting, footer, buttonText, faqs, sectionTitle } = config;
|
|
80
|
+
|
|
81
|
+
const content = {
|
|
82
|
+
text: greeting,
|
|
83
|
+
footer,
|
|
84
|
+
buttonText,
|
|
85
|
+
sections: [
|
|
86
|
+
{
|
|
87
|
+
title: sectionTitle,
|
|
88
|
+
rows: faqs.map(faq => ({
|
|
89
|
+
title: faq.title,
|
|
90
|
+
description: faq.description || '',
|
|
91
|
+
rowId: faq.id
|
|
92
|
+
}))
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (quotedMsg) {
|
|
98
|
+
content.quoted = quotedMsg;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return content;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Main factory
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* createWelcomeFlow attach a Meta Business-style welcome template to a socket.
|
|
108
|
+
*
|
|
109
|
+
* @param {object} sock - Baileys socket
|
|
110
|
+
* @param {object} config - Partial config merges with DEFAULT_CONFIG
|
|
111
|
+
* @returns {{ listen, stop, reset, resetAll }}
|
|
112
|
+
*/
|
|
113
|
+
export const createWelcomeFlow = (sock, config = {}) => {
|
|
114
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
115
|
+
const seen = createSeenStore(cfg.persistPath);
|
|
116
|
+
|
|
117
|
+
// FAQ reply handler called when user selects a list option
|
|
118
|
+
const handleFaqReply = async (msg) => {
|
|
119
|
+
if (typeof cfg.onFaqReply !== 'function') return;
|
|
120
|
+
const reply = msg.message?.listResponseMessage;
|
|
121
|
+
if (!reply) return;
|
|
122
|
+
const faqId = reply.singleSelectReply?.selectedRowId;
|
|
123
|
+
const jid = jidNormalizedUser(msg.key.remoteJid);
|
|
124
|
+
if (faqId) {
|
|
125
|
+
await cfg.onFaqReply(jid, faqId, msg);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// First-message handler
|
|
130
|
+
const handleMessage = async ({ messages, type }) => {
|
|
131
|
+
if (type !== 'notify') return;
|
|
132
|
+
|
|
133
|
+
for (const msg of messages) {
|
|
134
|
+
try {
|
|
135
|
+
const jid = msg.key?.remoteJid;
|
|
136
|
+
if (!jid || msg.key?.fromMe) continue;
|
|
137
|
+
|
|
138
|
+
// Skip ignored JID types
|
|
139
|
+
if (cfg.ignoreGroups && isJidGroup(jid)) continue;
|
|
140
|
+
if (cfg.ignoreNewsletter && isJidNewsletter(jid)) continue;
|
|
141
|
+
if (cfg.ignoreBroadcast && isJidBroadcast(jid)) continue;
|
|
142
|
+
|
|
143
|
+
const normalizedJid = jidNormalizedUser(jid);
|
|
144
|
+
|
|
145
|
+
// Check if it's a FAQ reply first
|
|
146
|
+
if (msg.message?.listResponseMessage) {
|
|
147
|
+
await handleFaqReply(msg);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Already greeted this contact skip
|
|
152
|
+
if (seen.has(normalizedJid)) continue;
|
|
153
|
+
|
|
154
|
+
// Mark as seen immediately to prevent race conditions
|
|
155
|
+
seen.add(normalizedJid);
|
|
156
|
+
|
|
157
|
+
// Simulate typing before the greeting
|
|
158
|
+
if (cfg.typingDelayMs > 0) {
|
|
159
|
+
await sock.sendPresenceUpdate('composing', jid);
|
|
160
|
+
await delay(cfg.typingDelayMs);
|
|
161
|
+
await sock.sendPresenceUpdate('paused', jid);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Send the welcome message
|
|
165
|
+
const welcomeContent = buildWelcomeMessage(cfg, msg);
|
|
166
|
+
await sock.sendMessage(jid, welcomeContent);
|
|
167
|
+
|
|
168
|
+
// Fire onGreet callback if provided
|
|
169
|
+
if (typeof cfg.onGreet === 'function') {
|
|
170
|
+
await cfg.onGreet(normalizedJid, msg).catch(() => {});
|
|
171
|
+
}
|
|
172
|
+
} catch (_) {}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Public API
|
|
177
|
+
return {
|
|
178
|
+
/**
|
|
179
|
+
* Start listening for new contacts.
|
|
180
|
+
*/
|
|
181
|
+
listen() {
|
|
182
|
+
sock.ev.on('messages.upsert', handleMessage);
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Stop listening.
|
|
187
|
+
*/
|
|
188
|
+
stop() {
|
|
189
|
+
sock.ev.off('messages.upsert', handleMessage);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Force re-greet a specific contact next time they message.
|
|
194
|
+
* @param {string} jid
|
|
195
|
+
*/
|
|
196
|
+
reset(jid) {
|
|
197
|
+
seen.delete(jidNormalizedUser(jid));
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Clear all seen contacts everyone gets re-greeted.
|
|
202
|
+
*/
|
|
203
|
+
resetAll() {
|
|
204
|
+
seen.clear();
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Check if a contact has already been greeted.
|
|
209
|
+
* @param {string} jid
|
|
210
|
+
* @returns {boolean}
|
|
211
|
+
*/
|
|
212
|
+
hasGreeted(jid) {
|
|
213
|
+
return seen.has(jidNormalizedUser(jid));
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|