baileys-redis-auth 1.0.0
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/.eslintrc +30 -0
- package/.prettierrc +8 -0
- package/.vscode/settings.json +20 -0
- package/README.md +1 -0
- package/lib/package.json +39 -0
- package/lib/src/Example/example.d.ts +1 -0
- package/lib/src/Example/example.js +195 -0
- package/lib/src/Example/logger-pino.d.ts +17 -0
- package/lib/src/Example/logger-pino.js +39 -0
- package/lib/src/index.d.ts +9 -0
- package/lib/src/index.js +65 -0
- package/package.json +39 -0
- package/src/Example/example.ts +220 -0
- package/src/Example/logger-pino.ts +38 -0
- package/src/index.ts +76 -0
- package/tsconfig.json +21 -0
package/.eslintrc
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"node": true,
|
|
4
|
+
"browser": true,
|
|
5
|
+
"es6": true
|
|
6
|
+
},
|
|
7
|
+
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
|
8
|
+
"globals": {
|
|
9
|
+
"Atomics": "readonly",
|
|
10
|
+
"SharedArrayBuffer": "readonly"
|
|
11
|
+
},
|
|
12
|
+
"parserOptions": {
|
|
13
|
+
"ecmaVersion": 2018,
|
|
14
|
+
"sourceType": "module"
|
|
15
|
+
},
|
|
16
|
+
"rules": {
|
|
17
|
+
"prettier/prettier": [
|
|
18
|
+
"error",
|
|
19
|
+
{
|
|
20
|
+
"singleQuote": true,
|
|
21
|
+
"trailingComa": "es5",
|
|
22
|
+
"bracketSpacing": false,
|
|
23
|
+
"printWidth": 100,
|
|
24
|
+
"tabWidth": 2,
|
|
25
|
+
"semi": true
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"eqeqeq": ["error", "always"]
|
|
29
|
+
}
|
|
30
|
+
}
|
package/.prettierrc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"[javascript]": {
|
|
4
|
+
"editor.codeActionsOnSave": {
|
|
5
|
+
"source.fixAll.eslint": false
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
"files.autoSave": "afterDelay",
|
|
9
|
+
"workbench.editor.highlightModifiedTabs": true,
|
|
10
|
+
"editor.cursorStyle": "block",
|
|
11
|
+
"editor.cursorBlinking": "smooth",
|
|
12
|
+
"files.trimFinalNewlines": true,
|
|
13
|
+
"codemetrics.nodeconfiguration.JsxElement": 0,
|
|
14
|
+
"codemetrics.nodeconfiguration.JsxSelfClosingElement": 0,
|
|
15
|
+
"editor.fontFamily": "Fira Code",
|
|
16
|
+
"editor.fontLigatures": true,
|
|
17
|
+
"editor.codeActionsOnSave": {
|
|
18
|
+
"source.fixAll.eslint": true
|
|
19
|
+
}
|
|
20
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# baileys-redis-auth
|
package/lib/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "baileys-redis-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Redis Auth for Baileys",
|
|
5
|
+
"author": "heriyanto binduni <hbinduni@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "lib/index.ts",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/WhiskeySockets/Baileys",
|
|
10
|
+
"repository": {
|
|
11
|
+
"url": "git@github.com:WhiskeySockets/Baileys.git"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"example": "node --inspect -r ts-node/register src/Example/example.ts --no-store --no-reply"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"baileys",
|
|
19
|
+
"redis",
|
|
20
|
+
"auth"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@adiwajshing/keyed-db": "^0.2.4",
|
|
24
|
+
"@hapi/boom": "^10.0.1",
|
|
25
|
+
"@types/node": "^20.2.5",
|
|
26
|
+
"eslint": "^8.41.0",
|
|
27
|
+
"eslint-config-prettier": "^8.8.0",
|
|
28
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
29
|
+
"node-cache": "^5.1.2",
|
|
30
|
+
"pino": "^8.14.1",
|
|
31
|
+
"pino-pretty": "^10.0.0",
|
|
32
|
+
"ts-node": "^10.9.1",
|
|
33
|
+
"typescript": "^5.0.4"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@whiskeysockets/baileys": "^6.1.0",
|
|
37
|
+
"ioredis": "^5.3.2"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
30
|
+
const baileys_1 = __importStar(require("@whiskeysockets/baileys"));
|
|
31
|
+
const logger_pino_1 = require("./logger-pino");
|
|
32
|
+
const index_1 = require("../index");
|
|
33
|
+
logger_pino_1.logger.level = 'silent';
|
|
34
|
+
const useStore = !process.argv.includes('--no-store');
|
|
35
|
+
const doReplies = !process.argv.includes('--no-reply');
|
|
36
|
+
// external map to store retry counts of messages when decryption/encryption fails
|
|
37
|
+
// keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts
|
|
38
|
+
const msgRetryCounterCache = new node_cache_1.default();
|
|
39
|
+
// the store maintains the data of the WA connection in memory
|
|
40
|
+
// can be written out to a file & read from it
|
|
41
|
+
const store = useStore ? (0, baileys_1.makeInMemoryStore)({ logger: logger_pino_1.logger }) : undefined;
|
|
42
|
+
store === null || store === void 0 ? void 0 : store.readFromFile('./baileys_store_multi.json');
|
|
43
|
+
// save every 10s
|
|
44
|
+
setInterval(() => {
|
|
45
|
+
store === null || store === void 0 ? void 0 : store.writeToFile('./baileys_store_multi.json');
|
|
46
|
+
}, 10000);
|
|
47
|
+
// start a connection
|
|
48
|
+
const startSock = async () => {
|
|
49
|
+
const redisOptions = {
|
|
50
|
+
host: 'localhost',
|
|
51
|
+
port: 6379,
|
|
52
|
+
password: 'd334911fd345f1170b5bfcc8e75ee72df0f114eb',
|
|
53
|
+
};
|
|
54
|
+
const { state, saveCreds } = await (0, index_1.useRedisAuthState)(redisOptions, 'DB1');
|
|
55
|
+
// fetch latest version of WA Web
|
|
56
|
+
const { version, isLatest } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
57
|
+
console.log(`using WA v${version.join('.')}, isLatest: ${isLatest}`);
|
|
58
|
+
const sock = (0, baileys_1.default)({
|
|
59
|
+
version,
|
|
60
|
+
logger: logger_pino_1.logger,
|
|
61
|
+
printQRInTerminal: true,
|
|
62
|
+
auth: {
|
|
63
|
+
creds: state.creds,
|
|
64
|
+
/** caching makes the store faster to send/recv messages */
|
|
65
|
+
keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys, logger_pino_1.logger),
|
|
66
|
+
},
|
|
67
|
+
msgRetryCounterCache,
|
|
68
|
+
generateHighQualityLinkPreview: true,
|
|
69
|
+
// ignore all broadcast messages -- to receive the same
|
|
70
|
+
// comment the line below out
|
|
71
|
+
// shouldIgnoreJid: jid => isJidBroadcast(jid),
|
|
72
|
+
// implement to handle retries & poll updates
|
|
73
|
+
getMessage,
|
|
74
|
+
});
|
|
75
|
+
store === null || store === void 0 ? void 0 : store.bind(sock.ev);
|
|
76
|
+
const sendMessageWTyping = async (msg, jid) => {
|
|
77
|
+
await sock.presenceSubscribe(jid);
|
|
78
|
+
await (0, baileys_1.delay)(500);
|
|
79
|
+
await sock.sendPresenceUpdate('composing', jid);
|
|
80
|
+
await (0, baileys_1.delay)(2000);
|
|
81
|
+
await sock.sendPresenceUpdate('paused', jid);
|
|
82
|
+
await sock.sendMessage(jid, msg);
|
|
83
|
+
};
|
|
84
|
+
// the process function lets you process all events that just occurred
|
|
85
|
+
// efficiently in a batch
|
|
86
|
+
sock.ev.process(
|
|
87
|
+
// events is a map for event name => event data
|
|
88
|
+
async (events) => {
|
|
89
|
+
var _a, _b, _c;
|
|
90
|
+
// something about the connection changed
|
|
91
|
+
// maybe it closed, or we received all offline message or connection opened
|
|
92
|
+
if (events['connection.update']) {
|
|
93
|
+
const update = events['connection.update'];
|
|
94
|
+
const { connection, lastDisconnect } = update;
|
|
95
|
+
if (connection === 'close') {
|
|
96
|
+
// reconnect if not logged out
|
|
97
|
+
if (((_b = (_a = lastDisconnect === null || lastDisconnect === void 0 ? void 0 : lastDisconnect.error) === null || _a === void 0 ? void 0 : _a.output) === null || _b === void 0 ? void 0 : _b.statusCode) !== baileys_1.DisconnectReason.loggedOut) {
|
|
98
|
+
startSock();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log('Connection closed. You are logged out.');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
console.log('connection update', update);
|
|
105
|
+
}
|
|
106
|
+
// credentials updated -- save them
|
|
107
|
+
if (events['creds.update']) {
|
|
108
|
+
await saveCreds();
|
|
109
|
+
}
|
|
110
|
+
if (events['labels.association']) {
|
|
111
|
+
console.log(events['labels.association']);
|
|
112
|
+
}
|
|
113
|
+
if (events['labels.edit']) {
|
|
114
|
+
console.log(events['labels.edit']);
|
|
115
|
+
}
|
|
116
|
+
if (events.call) {
|
|
117
|
+
console.log('recv call event', events.call);
|
|
118
|
+
}
|
|
119
|
+
// history received
|
|
120
|
+
if (events['messaging-history.set']) {
|
|
121
|
+
const { chats, contacts, messages, isLatest } = events['messaging-history.set'];
|
|
122
|
+
console.log(`recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest})`);
|
|
123
|
+
}
|
|
124
|
+
// received a new message
|
|
125
|
+
if (events['messages.upsert']) {
|
|
126
|
+
const upsert = events['messages.upsert'];
|
|
127
|
+
// console.log('recv messages ', JSON.stringify(upsert, undefined, 2));
|
|
128
|
+
if (upsert.type === 'notify') {
|
|
129
|
+
for (const msg of upsert.messages) {
|
|
130
|
+
if (!msg.key.fromMe) {
|
|
131
|
+
if (doReplies) {
|
|
132
|
+
console.log('replying to', msg.key.remoteJid);
|
|
133
|
+
await sock.readMessages([msg.key]);
|
|
134
|
+
await sendMessageWTyping({ text: 'Hello there!' }, msg.key.remoteJid);
|
|
135
|
+
}
|
|
136
|
+
if (((_c = msg.message) === null || _c === void 0 ? void 0 : _c.conversation) === 'ping') {
|
|
137
|
+
await sock.readMessages([msg.key]);
|
|
138
|
+
await sendMessageWTyping({ text: 'Ping!' }, msg.key.remoteJid);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// messages updated like status delivered, message deleted etc.
|
|
145
|
+
if (events['messages.update']) {
|
|
146
|
+
// console.log(JSON.stringify(events['messages.update'], undefined, 2));
|
|
147
|
+
for (const { key, update } of events['messages.update']) {
|
|
148
|
+
if (update.pollUpdates) {
|
|
149
|
+
const pollCreation = await getMessage(key);
|
|
150
|
+
if (pollCreation) {
|
|
151
|
+
console.log('got poll update, aggregation: ', (0, baileys_1.getAggregateVotesInPollMessage)({
|
|
152
|
+
message: pollCreation,
|
|
153
|
+
pollUpdates: update.pollUpdates,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (events['message-receipt.update']) {
|
|
160
|
+
// console.log(events['message-receipt.update']);
|
|
161
|
+
}
|
|
162
|
+
if (events['messages.reaction']) {
|
|
163
|
+
// console.log(events['messages.reaction']);
|
|
164
|
+
}
|
|
165
|
+
if (events['presence.update']) {
|
|
166
|
+
// console.log(events['presence.update']);
|
|
167
|
+
}
|
|
168
|
+
if (events['chats.update']) {
|
|
169
|
+
// console.log(events['chats.update']);
|
|
170
|
+
}
|
|
171
|
+
if (events['contacts.update']) {
|
|
172
|
+
for (const contact of events['contacts.update']) {
|
|
173
|
+
if (typeof contact.imgUrl !== 'undefined') {
|
|
174
|
+
const newUrl = contact.imgUrl === null
|
|
175
|
+
? null
|
|
176
|
+
: await sock.profilePictureUrl(contact.id).catch(() => null);
|
|
177
|
+
console.log(`contact ${contact.id} has a new profile pic: ${newUrl}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (events['chats.delete']) {
|
|
182
|
+
console.log('chats deleted ', events['chats.delete']);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return sock;
|
|
186
|
+
async function getMessage(key) {
|
|
187
|
+
if (store) {
|
|
188
|
+
const msg = await store.loadMessage(key.remoteJid, key.id);
|
|
189
|
+
return (msg === null || msg === void 0 ? void 0 : msg.message) || undefined;
|
|
190
|
+
}
|
|
191
|
+
// only if store is present
|
|
192
|
+
return baileys_1.proto.Message.fromObject({});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
startSock();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const logger: import("pino").Logger<{
|
|
2
|
+
level: string;
|
|
3
|
+
formatters: {
|
|
4
|
+
bindings: (bindings: any) => {
|
|
5
|
+
pid: any;
|
|
6
|
+
host: any;
|
|
7
|
+
app: string;
|
|
8
|
+
v: string;
|
|
9
|
+
node_version: string;
|
|
10
|
+
};
|
|
11
|
+
level: (label: any, number: any) => {
|
|
12
|
+
level: any;
|
|
13
|
+
label: any;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
timestamp: () => string;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
const pino_1 = __importDefault(require("pino"));
|
|
8
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
9
|
+
const pretty = {
|
|
10
|
+
level: 'info',
|
|
11
|
+
target: 'pino-pretty',
|
|
12
|
+
options: {
|
|
13
|
+
colorize: true,
|
|
14
|
+
translateTime: 'SYS:isoDateTime',
|
|
15
|
+
ignore: 'pid,hostname',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const transports = pino_1.default.transport({
|
|
19
|
+
targets: [pretty],
|
|
20
|
+
});
|
|
21
|
+
const options = {
|
|
22
|
+
level: process.env.PINO_LOG_LEVEL || 'info',
|
|
23
|
+
formatters: {
|
|
24
|
+
bindings: (bindings) => {
|
|
25
|
+
return {
|
|
26
|
+
pid: bindings.pid,
|
|
27
|
+
host: bindings.hostname,
|
|
28
|
+
app: package_json_1.default.name,
|
|
29
|
+
v: package_json_1.default.version,
|
|
30
|
+
node_version: process.version,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
level: (label, number) => {
|
|
34
|
+
return { level: number, label: label.toUpperCase() };
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
timestamp: pino_1.default.stdTimeFunctions.isoTime,
|
|
38
|
+
};
|
|
39
|
+
exports.logger = (0, pino_1.default)(options, transports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RedisOptions } from 'ioredis';
|
|
2
|
+
import { AuthenticationState } from '@whiskeysockets/baileys';
|
|
3
|
+
/**
|
|
4
|
+
* Stores the full authentication state in Redis.
|
|
5
|
+
* */
|
|
6
|
+
export declare const useRedisAuthState: (redisOptions: RedisOptions, prefix?: string) => Promise<{
|
|
7
|
+
state: AuthenticationState;
|
|
8
|
+
saveCreds: () => Promise<void>;
|
|
9
|
+
}>;
|
package/lib/src/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.useRedisAuthState = void 0;
|
|
7
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
8
|
+
const baileys_1 = require("@whiskeysockets/baileys");
|
|
9
|
+
/**
|
|
10
|
+
* Stores the full authentication state in Redis.
|
|
11
|
+
* */
|
|
12
|
+
const useRedisAuthState = async (redisOptions, prefix = 'DB1') => {
|
|
13
|
+
const redis = new ioredis_1.default(redisOptions);
|
|
14
|
+
const writeData = (data, key) => {
|
|
15
|
+
return redis.set(`${prefix}:${key}`, JSON.stringify(data, baileys_1.BufferJSON.replacer));
|
|
16
|
+
};
|
|
17
|
+
const readData = async (key) => {
|
|
18
|
+
try {
|
|
19
|
+
const data = await redis.get(`${prefix}:${key}`);
|
|
20
|
+
return data ? JSON.parse(data, baileys_1.BufferJSON.reviver) : null;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const creds = (await readData('creds')) || (0, baileys_1.initAuthCreds)();
|
|
27
|
+
return {
|
|
28
|
+
state: {
|
|
29
|
+
creds,
|
|
30
|
+
keys: {
|
|
31
|
+
get: async (type, ids) => {
|
|
32
|
+
const data = {};
|
|
33
|
+
await Promise.all(ids.map(async (id) => {
|
|
34
|
+
let value = await readData(`${type}-${id}`);
|
|
35
|
+
if (type === `${prefix}:app-state-sync-key` && value) {
|
|
36
|
+
value = baileys_1.proto.Message.AppStateSyncKeyData.fromObject(value);
|
|
37
|
+
}
|
|
38
|
+
data[id] = value;
|
|
39
|
+
}));
|
|
40
|
+
return data;
|
|
41
|
+
},
|
|
42
|
+
set: async (data) => {
|
|
43
|
+
const pipeline = redis.pipeline();
|
|
44
|
+
for (const category in data) {
|
|
45
|
+
for (const id in data[category]) {
|
|
46
|
+
const value = data[category][id];
|
|
47
|
+
const key = `${prefix}:${category}-${id}`;
|
|
48
|
+
if (value) {
|
|
49
|
+
pipeline.set(key, JSON.stringify(value, baileys_1.BufferJSON.replacer));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
pipeline.del(key);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
await pipeline.exec();
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
saveCreds: async () => {
|
|
61
|
+
await writeData(creds, 'creds');
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
exports.useRedisAuthState = useRedisAuthState;
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "baileys-redis-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Redis Auth for Baileys",
|
|
5
|
+
"author": "heriyanto binduni <hbinduni@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "lib/index.ts",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/hbinduni/baileys-redis-auth",
|
|
10
|
+
"repository": {
|
|
11
|
+
"url": "https://github.com/hbinduni/baileys-redis-auth.git"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"example": "node --inspect -r ts-node/register src/Example/example.ts --no-store --no-reply"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"baileys",
|
|
19
|
+
"redis",
|
|
20
|
+
"auth"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@adiwajshing/keyed-db": "^0.2.4",
|
|
24
|
+
"@hapi/boom": "^10.0.1",
|
|
25
|
+
"@types/node": "^20.2.5",
|
|
26
|
+
"eslint": "^8.41.0",
|
|
27
|
+
"eslint-config-prettier": "^8.8.0",
|
|
28
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
29
|
+
"node-cache": "^5.1.2",
|
|
30
|
+
"pino": "^8.14.1",
|
|
31
|
+
"pino-pretty": "^10.0.0",
|
|
32
|
+
"ts-node": "^10.9.1",
|
|
33
|
+
"typescript": "^5.0.4"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@whiskeysockets/baileys": "^6.1.0",
|
|
37
|
+
"ioredis": "^5.3.2"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {Boom} from '@hapi/boom';
|
|
2
|
+
import NodeCache from 'node-cache';
|
|
3
|
+
import makeWASocket, {
|
|
4
|
+
AnyMessageContent,
|
|
5
|
+
delay,
|
|
6
|
+
DisconnectReason,
|
|
7
|
+
fetchLatestBaileysVersion,
|
|
8
|
+
getAggregateVotesInPollMessage,
|
|
9
|
+
makeCacheableSignalKeyStore,
|
|
10
|
+
makeInMemoryStore,
|
|
11
|
+
proto,
|
|
12
|
+
useMultiFileAuthState,
|
|
13
|
+
WAMessageContent,
|
|
14
|
+
WAMessageKey,
|
|
15
|
+
} from '@whiskeysockets/baileys';
|
|
16
|
+
import {logger} from './logger-pino';
|
|
17
|
+
import {useRedisAuthState} from '../index';
|
|
18
|
+
|
|
19
|
+
logger.level = 'silent';
|
|
20
|
+
|
|
21
|
+
const useStore = !process.argv.includes('--no-store');
|
|
22
|
+
const doReplies = !process.argv.includes('--no-reply');
|
|
23
|
+
|
|
24
|
+
// external map to store retry counts of messages when decryption/encryption fails
|
|
25
|
+
// keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts
|
|
26
|
+
const msgRetryCounterCache = new NodeCache();
|
|
27
|
+
|
|
28
|
+
// the store maintains the data of the WA connection in memory
|
|
29
|
+
// can be written out to a file & read from it
|
|
30
|
+
const store = useStore ? makeInMemoryStore({logger}) : undefined;
|
|
31
|
+
store?.readFromFile('./baileys_store_multi.json');
|
|
32
|
+
// save every 10s
|
|
33
|
+
setInterval(() => {
|
|
34
|
+
store?.writeToFile('./baileys_store_multi.json');
|
|
35
|
+
}, 10_000);
|
|
36
|
+
|
|
37
|
+
// start a connection
|
|
38
|
+
const startSock = async () => {
|
|
39
|
+
const redisOptions = {
|
|
40
|
+
host: 'localhost',
|
|
41
|
+
port: 6379,
|
|
42
|
+
password: 'd334911fd345f1170b5bfcc8e75ee72df0f114eb',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const {state, saveCreds} = await useRedisAuthState(redisOptions, 'DB1');
|
|
46
|
+
|
|
47
|
+
// fetch latest version of WA Web
|
|
48
|
+
const {version, isLatest} = await fetchLatestBaileysVersion();
|
|
49
|
+
console.log(`using WA v${version.join('.')}, isLatest: ${isLatest}`);
|
|
50
|
+
|
|
51
|
+
const sock = makeWASocket({
|
|
52
|
+
version,
|
|
53
|
+
logger,
|
|
54
|
+
printQRInTerminal: true,
|
|
55
|
+
auth: {
|
|
56
|
+
creds: state.creds,
|
|
57
|
+
/** caching makes the store faster to send/recv messages */
|
|
58
|
+
keys: makeCacheableSignalKeyStore(state.keys, logger),
|
|
59
|
+
},
|
|
60
|
+
msgRetryCounterCache,
|
|
61
|
+
generateHighQualityLinkPreview: true,
|
|
62
|
+
// ignore all broadcast messages -- to receive the same
|
|
63
|
+
// comment the line below out
|
|
64
|
+
// shouldIgnoreJid: jid => isJidBroadcast(jid),
|
|
65
|
+
// implement to handle retries & poll updates
|
|
66
|
+
getMessage,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
store?.bind(sock.ev);
|
|
70
|
+
|
|
71
|
+
const sendMessageWTyping = async (msg: AnyMessageContent, jid: string) => {
|
|
72
|
+
await sock.presenceSubscribe(jid);
|
|
73
|
+
await delay(500);
|
|
74
|
+
|
|
75
|
+
await sock.sendPresenceUpdate('composing', jid);
|
|
76
|
+
await delay(2000);
|
|
77
|
+
|
|
78
|
+
await sock.sendPresenceUpdate('paused', jid);
|
|
79
|
+
|
|
80
|
+
await sock.sendMessage(jid, msg);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// the process function lets you process all events that just occurred
|
|
84
|
+
// efficiently in a batch
|
|
85
|
+
sock.ev.process(
|
|
86
|
+
// events is a map for event name => event data
|
|
87
|
+
async (events) => {
|
|
88
|
+
// something about the connection changed
|
|
89
|
+
// maybe it closed, or we received all offline message or connection opened
|
|
90
|
+
if (events['connection.update']) {
|
|
91
|
+
const update = events['connection.update'];
|
|
92
|
+
const {connection, lastDisconnect} = update;
|
|
93
|
+
if (connection === 'close') {
|
|
94
|
+
// reconnect if not logged out
|
|
95
|
+
if ((lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut) {
|
|
96
|
+
startSock();
|
|
97
|
+
} else {
|
|
98
|
+
console.log('Connection closed. You are logged out.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('connection update', update);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// credentials updated -- save them
|
|
106
|
+
if (events['creds.update']) {
|
|
107
|
+
await saveCreds();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (events['labels.association']) {
|
|
111
|
+
console.log(events['labels.association']);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (events['labels.edit']) {
|
|
115
|
+
console.log(events['labels.edit']);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (events.call) {
|
|
119
|
+
console.log('recv call event', events.call);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// history received
|
|
123
|
+
if (events['messaging-history.set']) {
|
|
124
|
+
const {chats, contacts, messages, isLatest} = events['messaging-history.set'];
|
|
125
|
+
console.log(
|
|
126
|
+
`recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest})`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// received a new message
|
|
131
|
+
if (events['messages.upsert']) {
|
|
132
|
+
const upsert = events['messages.upsert'];
|
|
133
|
+
// console.log('recv messages ', JSON.stringify(upsert, undefined, 2));
|
|
134
|
+
|
|
135
|
+
if (upsert.type === 'notify') {
|
|
136
|
+
for (const msg of upsert.messages) {
|
|
137
|
+
if (!msg.key.fromMe) {
|
|
138
|
+
if (doReplies) {
|
|
139
|
+
console.log('replying to', msg.key.remoteJid);
|
|
140
|
+
await sock!.readMessages([msg.key]);
|
|
141
|
+
await sendMessageWTyping({text: 'Hello there!'}, msg.key.remoteJid!);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (msg.message?.conversation === 'ping') {
|
|
145
|
+
await sock!.readMessages([msg.key]);
|
|
146
|
+
await sendMessageWTyping({text: 'Ping!'}, msg.key.remoteJid!);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// messages updated like status delivered, message deleted etc.
|
|
154
|
+
if (events['messages.update']) {
|
|
155
|
+
// console.log(JSON.stringify(events['messages.update'], undefined, 2));
|
|
156
|
+
|
|
157
|
+
for (const {key, update} of events['messages.update']) {
|
|
158
|
+
if (update.pollUpdates) {
|
|
159
|
+
const pollCreation = await getMessage(key);
|
|
160
|
+
if (pollCreation) {
|
|
161
|
+
console.log(
|
|
162
|
+
'got poll update, aggregation: ',
|
|
163
|
+
getAggregateVotesInPollMessage({
|
|
164
|
+
message: pollCreation,
|
|
165
|
+
pollUpdates: update.pollUpdates,
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (events['message-receipt.update']) {
|
|
174
|
+
// console.log(events['message-receipt.update']);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (events['messages.reaction']) {
|
|
178
|
+
// console.log(events['messages.reaction']);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (events['presence.update']) {
|
|
182
|
+
// console.log(events['presence.update']);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (events['chats.update']) {
|
|
186
|
+
// console.log(events['chats.update']);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (events['contacts.update']) {
|
|
190
|
+
for (const contact of events['contacts.update']) {
|
|
191
|
+
if (typeof contact.imgUrl !== 'undefined') {
|
|
192
|
+
const newUrl =
|
|
193
|
+
contact.imgUrl === null
|
|
194
|
+
? null
|
|
195
|
+
: await sock!.profilePictureUrl(contact.id!).catch(() => null);
|
|
196
|
+
console.log(`contact ${contact.id} has a new profile pic: ${newUrl}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (events['chats.delete']) {
|
|
202
|
+
console.log('chats deleted ', events['chats.delete']);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return sock;
|
|
208
|
+
|
|
209
|
+
async function getMessage(key: WAMessageKey): Promise<WAMessageContent | undefined> {
|
|
210
|
+
if (store) {
|
|
211
|
+
const msg = await store.loadMessage(key.remoteJid!, key.id!);
|
|
212
|
+
return msg?.message || undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// only if store is present
|
|
216
|
+
return proto.Message.fromObject({});
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
startSock();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
|
|
3
|
+
import Pack from '../../package.json';
|
|
4
|
+
|
|
5
|
+
const pretty = {
|
|
6
|
+
level: 'info',
|
|
7
|
+
target: 'pino-pretty',
|
|
8
|
+
options: {
|
|
9
|
+
colorize: true,
|
|
10
|
+
translateTime: 'SYS:isoDateTime',
|
|
11
|
+
ignore: 'pid,hostname',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const transports = pino.transport({
|
|
16
|
+
targets: [pretty],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const options = {
|
|
20
|
+
level: process.env.PINO_LOG_LEVEL || 'info',
|
|
21
|
+
formatters: {
|
|
22
|
+
bindings: (bindings) => {
|
|
23
|
+
return {
|
|
24
|
+
pid: bindings.pid,
|
|
25
|
+
host: bindings.hostname,
|
|
26
|
+
app: Pack.name,
|
|
27
|
+
v: Pack.version,
|
|
28
|
+
node_version: process.version,
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
level: (label, number) => {
|
|
32
|
+
return {level: number, label: label.toUpperCase()};
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const logger = pino(options, transports);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Redis, {RedisOptions} from 'ioredis';
|
|
2
|
+
import {
|
|
3
|
+
AuthenticationCreds,
|
|
4
|
+
AuthenticationState,
|
|
5
|
+
SignalDataTypeMap,
|
|
6
|
+
initAuthCreds,
|
|
7
|
+
BufferJSON,
|
|
8
|
+
proto,
|
|
9
|
+
} from '@whiskeysockets/baileys';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Stores the full authentication state in Redis.
|
|
13
|
+
* */
|
|
14
|
+
export const useRedisAuthState = async (
|
|
15
|
+
redisOptions: RedisOptions,
|
|
16
|
+
prefix: string = 'DB1'
|
|
17
|
+
): Promise<{state: AuthenticationState; saveCreds: () => Promise<void>}> => {
|
|
18
|
+
const redis = new Redis(redisOptions);
|
|
19
|
+
|
|
20
|
+
const writeData = (data: any, key: string) => {
|
|
21
|
+
return redis.set(`${prefix}:${key}`, JSON.stringify(data, BufferJSON.replacer));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const readData = async (key: string) => {
|
|
25
|
+
try {
|
|
26
|
+
const data = await redis.get(`${prefix}:${key}`);
|
|
27
|
+
return data ? JSON.parse(data, BufferJSON.reviver) : null;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
state: {
|
|
37
|
+
creds,
|
|
38
|
+
keys: {
|
|
39
|
+
get: async (type, ids) => {
|
|
40
|
+
const data: {[_: string]: SignalDataTypeMap[typeof type]} = {};
|
|
41
|
+
await Promise.all(
|
|
42
|
+
ids.map(async (id) => {
|
|
43
|
+
let value = await readData(`${type}-${id}`);
|
|
44
|
+
if (type === `${prefix}:app-state-sync-key` && value) {
|
|
45
|
+
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
data[id] = value;
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return data;
|
|
53
|
+
},
|
|
54
|
+
set: async (data) => {
|
|
55
|
+
const pipeline = redis.pipeline();
|
|
56
|
+
for (const category in data) {
|
|
57
|
+
for (const id in data[category]) {
|
|
58
|
+
const value = data[category][id];
|
|
59
|
+
const key = `${prefix}:${category}-${id}`;
|
|
60
|
+
if (value) {
|
|
61
|
+
pipeline.set(key, JSON.stringify(value, BufferJSON.replacer));
|
|
62
|
+
} else {
|
|
63
|
+
pipeline.del(key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await pipeline.exec();
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
saveCreds: async () => {
|
|
73
|
+
await writeData(creds, 'creds');
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2018",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"experimentalDecorators": true,
|
|
6
|
+
"allowJs": false,
|
|
7
|
+
"checkJs": false,
|
|
8
|
+
"outDir": "lib",
|
|
9
|
+
"strict": false,
|
|
10
|
+
"strictNullChecks": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"noImplicitThis": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"declaration": true,
|
|
17
|
+
"lib": ["es2020", "esnext.array", "DOM"]
|
|
18
|
+
},
|
|
19
|
+
"include": ["./src/**/*.ts"],
|
|
20
|
+
"exclude": ["node_modules", "src/__test__/*"]
|
|
21
|
+
}
|