whatsapp-rpc 0.0.6 → 0.0.9
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/README.md +34 -4
- package/configs/config.yaml +1 -1
- package/package.json +4 -2
- package/scripts/cli.js +86 -19
- package/scripts/clean.cjs +0 -63
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# WhatsApp WEB RPC
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/whatsapp-rpc)
|
|
4
|
+
[](https://pypi.org/project/whatsapp-rpc/)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
6
|
|
|
6
7
|
WebSocket JSON-RPC 2.0 API for QR Based WhatsApp Web.
|
|
@@ -64,7 +65,6 @@ Data Storage:
|
|
|
64
65
|
┌─────────────────────────────────────────────────────────┐
|
|
65
66
|
│ data/ │
|
|
66
67
|
│ ├── whatsapp.db (SQLite: session, contacts, history)│
|
|
67
|
-
│ ├── qr/*.png (QR code images) │
|
|
68
68
|
│ └── groups.json (Group cache) │
|
|
69
69
|
└─────────────────────────────────────────────────────────┘
|
|
70
70
|
```
|
|
@@ -78,6 +78,9 @@ npx whatsapp-rpc restart # Restart API server
|
|
|
78
78
|
npx whatsapp-rpc status # Check if running
|
|
79
79
|
npx whatsapp-rpc api --foreground # Run in foreground (for Docker)
|
|
80
80
|
npx whatsapp-rpc build # Build from source (requires Go)
|
|
81
|
+
|
|
82
|
+
# Custom port
|
|
83
|
+
npx whatsapp-rpc start --port 8080
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
## Docker
|
|
@@ -106,7 +109,7 @@ Connect via WebSocket to `ws://localhost:9400/ws/rpc` and send JSON-RPC 2.0 requ
|
|
|
106
109
|
| `restart` | Full reset: logout, delete DB, cleanup, start fresh |
|
|
107
110
|
| `reset` | Reset session (logout and delete credentials) |
|
|
108
111
|
| `diagnostics` | Get detailed diagnostics information |
|
|
109
|
-
| `qr` | Get QR code for pairing (code,
|
|
112
|
+
| `qr` | Get QR code for pairing (code, image_data as base64 PNG) |
|
|
110
113
|
|
|
111
114
|
### Messaging
|
|
112
115
|
|
|
@@ -172,7 +175,7 @@ The server pushes events as JSON-RPC notifications (no `id` field):
|
|
|
172
175
|
| `event.connection_failure` | Connection failed |
|
|
173
176
|
| `event.logged_out` | User logged out |
|
|
174
177
|
| `event.temporary_ban` | Temporary ban received |
|
|
175
|
-
| `event.qr_code` | New QR code available (code,
|
|
178
|
+
| `event.qr_code` | New QR code available (code, image_data) |
|
|
176
179
|
| `event.message_sent` | Message sent successfully |
|
|
177
180
|
| `event.message_received` | New message received (includes forwarding info) |
|
|
178
181
|
| `event.history_sync_complete` | History sync completed after first login |
|
|
@@ -334,6 +337,7 @@ The server pushes events as JSON-RPC notifications (no `id` field):
|
|
|
334
337
|
| Variable | Default | Description |
|
|
335
338
|
|----------|---------|-------------|
|
|
336
339
|
| `PORT` | 9400 | WebSocket API port |
|
|
340
|
+
| `WHATSAPP_RPC_PORT` | 9400 | WebSocket API port (alternative to `PORT`) |
|
|
337
341
|
| `WHATSAPP_RPC_SKIP_BINARY_DOWNLOAD` | - | Set to `1` to skip binary download |
|
|
338
342
|
| `WHATSAPP_RPC_PREFER_SOURCE` | - | Set to `1` to build from source if Go is installed |
|
|
339
343
|
|
|
@@ -342,7 +346,6 @@ The server pushes events as JSON-RPC notifications (no `id` field):
|
|
|
342
346
|
| Path | Description |
|
|
343
347
|
|------|-------------|
|
|
344
348
|
| `data/whatsapp.db` | SQLite database (session, contacts, message history) |
|
|
345
|
-
| `data/qr/*.png` | QR code images for pairing |
|
|
346
349
|
| `data/groups.json` | Group cache (auto-generated on connection) |
|
|
347
350
|
|
|
348
351
|
## Building from Source
|
|
@@ -357,6 +360,33 @@ npm run build
|
|
|
357
360
|
|
|
358
361
|
See [schema.json](schema.json) for complete OpenRPC specification.
|
|
359
362
|
|
|
363
|
+
## Python Client
|
|
364
|
+
|
|
365
|
+
An async Python client is available on PyPI:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
pip install whatsapp-rpc
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
import asyncio
|
|
373
|
+
from whatsapp_rpc import WhatsAppRPCClient
|
|
374
|
+
|
|
375
|
+
async def main():
|
|
376
|
+
client = WhatsAppRPCClient("ws://localhost:9400/ws/rpc")
|
|
377
|
+
await client.connect()
|
|
378
|
+
|
|
379
|
+
status = await client.status()
|
|
380
|
+
print(status)
|
|
381
|
+
|
|
382
|
+
await client.send(phone="1234567890", type="text", message="Hello!")
|
|
383
|
+
await client.close()
|
|
384
|
+
|
|
385
|
+
asyncio.run(main())
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
See [src/python/README.md](src/python/README.md) for full Python client documentation.
|
|
389
|
+
|
|
360
390
|
## Requirements
|
|
361
391
|
|
|
362
392
|
- Node.js 18+
|
package/configs/config.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatsapp-rpc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WhatsApp WebSocket RPC Server with JSON-RPC 2.0 protocol",
|
|
6
6
|
"author": "Rohit G <trohitg@gmail.com>",
|
|
@@ -44,8 +44,10 @@
|
|
|
44
44
|
"status": "node scripts/cli.js status",
|
|
45
45
|
"api": "node scripts/cli.js api --foreground",
|
|
46
46
|
"web": "node scripts/cli.js web",
|
|
47
|
+
"prebuild": "node -e \"require('fs').existsSync('node_modules') || process.exit(1)\" || npm install",
|
|
47
48
|
"build": "node scripts/cli.js build",
|
|
48
|
-
"
|
|
49
|
+
"preclean": "node -e \"require('fs').existsSync('node_modules') || process.exit(1)\" || npm install",
|
|
50
|
+
"clean": "node scripts/cli.js clean"
|
|
49
51
|
},
|
|
50
52
|
"dependencies": {
|
|
51
53
|
"chalk": "^5.3.0",
|
package/scripts/cli.js
CHANGED
|
@@ -5,16 +5,25 @@ import { execa } from 'execa';
|
|
|
5
5
|
import killPort from 'kill-port';
|
|
6
6
|
import { Socket } from 'net';
|
|
7
7
|
import { execSync, spawn } from 'child_process';
|
|
8
|
-
import { existsSync, statSync, mkdirSync } from 'fs';
|
|
8
|
+
import { existsSync, statSync, mkdirSync, rmSync, readdirSync, unlinkSync, readFileSync } from 'fs';
|
|
9
9
|
import { dirname, join } from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const ROOT = join(__dirname, '..');
|
|
14
|
-
const
|
|
14
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
|
15
|
+
const DEFAULT_PORT = 9400;
|
|
15
16
|
const BIN = process.platform === 'win32' ? 'whatsapp-rpc-server.exe' : 'whatsapp-rpc-server';
|
|
16
17
|
const BIN_DIR = join(ROOT, 'bin');
|
|
17
18
|
|
|
19
|
+
// Get port from environment or default
|
|
20
|
+
const getPort = (opts = {}) => {
|
|
21
|
+
if (opts.port) return parseInt(opts.port, 10);
|
|
22
|
+
if (process.env.PORT) return parseInt(process.env.PORT, 10);
|
|
23
|
+
if (process.env.WHATSAPP_RPC_PORT) return parseInt(process.env.WHATSAPP_RPC_PORT, 10);
|
|
24
|
+
return DEFAULT_PORT;
|
|
25
|
+
};
|
|
26
|
+
|
|
18
27
|
const log = (m, c = 'blue') => console.log(chalk[c](m));
|
|
19
28
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
20
29
|
|
|
@@ -43,8 +52,11 @@ const hasGo = () => {
|
|
|
43
52
|
catch { return false; }
|
|
44
53
|
};
|
|
45
54
|
|
|
46
|
-
async function api(
|
|
47
|
-
|
|
55
|
+
async function api(opts = {}) {
|
|
56
|
+
const port = getPort(opts);
|
|
57
|
+
const foreground = opts.foreground || false;
|
|
58
|
+
|
|
59
|
+
if (await portUp(port)) { log(`API already running on port ${port}`, 'yellow'); return; }
|
|
48
60
|
const bin = join(BIN_DIR, BIN);
|
|
49
61
|
if (!existsSync(bin)) {
|
|
50
62
|
if (!hasGo()) {
|
|
@@ -59,27 +71,35 @@ async function api(foreground = false) {
|
|
|
59
71
|
await build();
|
|
60
72
|
}
|
|
61
73
|
|
|
74
|
+
// Pass port to Go binary via environment variable
|
|
75
|
+
const env = { ...process.env, WA_SERVER_PORT: String(port) };
|
|
76
|
+
|
|
62
77
|
if (foreground) {
|
|
63
78
|
// Run in foreground - will receive Ctrl+C signals
|
|
64
|
-
const proc = spawn(bin, [], { cwd: ROOT, stdio: 'inherit' });
|
|
79
|
+
const proc = spawn(bin, [], { cwd: ROOT, stdio: 'inherit', env });
|
|
65
80
|
proc.on('close', (code) => process.exit(code || 0));
|
|
66
81
|
process.on('SIGINT', () => { proc.kill('SIGINT'); });
|
|
67
82
|
process.on('SIGTERM', () => { proc.kill('SIGTERM'); });
|
|
68
|
-
log(`API running: ws://localhost:${
|
|
83
|
+
log(`API running: ws://localhost:${port}/ws/rpc`, 'green');
|
|
69
84
|
} else {
|
|
70
85
|
// Run detached in background
|
|
71
|
-
spawn(bin, [], { cwd: ROOT, detached: true, stdio: 'ignore' }).unref();
|
|
72
|
-
if (await wait(
|
|
86
|
+
spawn(bin, [], { cwd: ROOT, detached: true, stdio: 'ignore', env }).unref();
|
|
87
|
+
if (await wait(port)) log(`API started: ws://localhost:${port}/ws/rpc`, 'green');
|
|
73
88
|
else log('API failed to start', 'red');
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
|
|
77
|
-
async function start() { await api(); }
|
|
78
|
-
|
|
92
|
+
async function start(opts = {}) { await api(opts); }
|
|
93
|
+
|
|
94
|
+
async function stop(opts = {}) {
|
|
95
|
+
const port = getPort(opts);
|
|
96
|
+
await kill(port, 'API');
|
|
97
|
+
}
|
|
79
98
|
|
|
80
|
-
async function status() {
|
|
81
|
-
const
|
|
82
|
-
|
|
99
|
+
async function status(opts = {}) {
|
|
100
|
+
const port = getPort(opts);
|
|
101
|
+
const a = await portUp(port);
|
|
102
|
+
log(`API (${port}): ${a ? 'UP' : 'DOWN'}`, a ? 'green' : 'red');
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
async function build() {
|
|
@@ -110,11 +130,58 @@ async function build() {
|
|
|
110
130
|
log(`Built: ${BIN} (${(statSync(bin).size / 1024 / 1024).toFixed(1)}MB)`, 'green');
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
133
|
+
async function clean(opts = {}) {
|
|
134
|
+
const port = getPort(opts);
|
|
135
|
+
|
|
136
|
+
// Stop API server
|
|
137
|
+
log('Stopping API server...', 'blue');
|
|
138
|
+
await kill(port, 'API');
|
|
139
|
+
|
|
140
|
+
// Remove bin directory
|
|
141
|
+
if (existsSync(BIN_DIR)) {
|
|
142
|
+
rmSync(BIN_DIR, { recursive: true });
|
|
143
|
+
log('Removed bin/', 'green');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Remove data directory
|
|
147
|
+
const dataDir = join(ROOT, 'data');
|
|
148
|
+
if (existsSync(dataDir)) {
|
|
149
|
+
rmSync(dataDir, { recursive: true });
|
|
150
|
+
log('Removed data/', 'green');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Remove any .db files in project root (legacy locations)
|
|
154
|
+
readdirSync(ROOT).filter(f => f.endsWith('.db') || f.endsWith('.db-wal') || f.endsWith('.db-shm')).forEach(f => {
|
|
155
|
+
unlinkSync(join(ROOT, f));
|
|
156
|
+
log(`Removed ${f}`, 'green');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Remove node_modules
|
|
160
|
+
const nodeModules = join(ROOT, 'node_modules');
|
|
161
|
+
if (existsSync(nodeModules)) {
|
|
162
|
+
rmSync(nodeModules, { recursive: true });
|
|
163
|
+
log('Removed node_modules/', 'green');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Remove package-lock.json
|
|
167
|
+
const lockFile = join(ROOT, 'package-lock.json');
|
|
168
|
+
if (existsSync(lockFile)) {
|
|
169
|
+
unlinkSync(lockFile);
|
|
170
|
+
log('Removed package-lock.json', 'green');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
log('Clean complete', 'green');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Global port option for all commands
|
|
177
|
+
const portOption = ['-p, --port <port>', 'API port (default: 9400, or PORT/WHATSAPP_RPC_PORT env var)'];
|
|
178
|
+
|
|
179
|
+
program.name('whatsapp-rpc').version(pkg.version);
|
|
180
|
+
program.command('start').description('Start API server').option(...portOption).action(start);
|
|
181
|
+
program.command('stop').description('Stop API server').option(...portOption).action(stop);
|
|
182
|
+
program.command('restart').description('Restart API server').option(...portOption).action(async (opts) => { await stop(opts); await sleep(1000); await start(opts); });
|
|
183
|
+
program.command('status').description('Show server status').option(...portOption).action(status);
|
|
184
|
+
program.command('api').description('Start API server').option('-f, --foreground', 'Run in foreground').option(...portOption).action(api);
|
|
119
185
|
program.command('build').description('Build binary from source (requires Go)').action(build);
|
|
186
|
+
program.command('clean').description('Full cleanup (stop server, remove bin/, data/, node_modules/)').action(clean);
|
|
120
187
|
program.parse();
|
package/scripts/clean.cjs
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Standalone clean script - no npm dependencies required
|
|
3
|
-
// Uses CommonJS for compatibility when node_modules doesn't exist
|
|
4
|
-
const { existsSync, unlinkSync, readdirSync, rmSync } = require('fs');
|
|
5
|
-
const { join } = require('path');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
const ROOT = join(__dirname, '..');
|
|
9
|
-
const BIN = process.platform === 'win32' ? 'whatsapp-rpc-server.exe' : 'whatsapp-rpc-server';
|
|
10
|
-
const BIN_DIR = join(ROOT, 'bin');
|
|
11
|
-
const API_PORT = 9400;
|
|
12
|
-
|
|
13
|
-
const log = (msg, color) => {
|
|
14
|
-
const colors = { green: '\x1b[32m', blue: '\x1b[34m', yellow: '\x1b[33m', red: '\x1b[31m', reset: '\x1b[0m' };
|
|
15
|
-
console.log(`${colors[color] || ''}${msg}${colors.reset}`);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Kill process on port
|
|
19
|
-
const killPort = (port, name) => {
|
|
20
|
-
try {
|
|
21
|
-
if (process.platform === 'win32') {
|
|
22
|
-
execSync(`for /f "tokens=5" %a in ('netstat -aon ^| findstr :${port} ^| findstr LISTENING') do taskkill /F /PID %a`, { stdio: 'ignore', shell: 'cmd.exe' });
|
|
23
|
-
} else {
|
|
24
|
-
execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || true`, { stdio: 'ignore' });
|
|
25
|
-
}
|
|
26
|
-
log(`${name} stopped`, 'green');
|
|
27
|
-
} catch { log(`${name} not running`, 'yellow'); }
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
log('Stopping API server...', 'blue');
|
|
31
|
-
killPort(API_PORT, 'API');
|
|
32
|
-
|
|
33
|
-
// Remove binary
|
|
34
|
-
const bin = join(BIN_DIR, BIN);
|
|
35
|
-
if (existsSync(bin)) { unlinkSync(bin); log(`Removed ${BIN}`, 'green'); }
|
|
36
|
-
|
|
37
|
-
// Remove bin directory if empty
|
|
38
|
-
if (existsSync(BIN_DIR) && readdirSync(BIN_DIR).length === 0) {
|
|
39
|
-
rmSync(BIN_DIR, { recursive: true }); log('Removed bin/', 'green');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Remove data directory (database, QR codes, etc.)
|
|
43
|
-
const dataDir = join(ROOT, 'data');
|
|
44
|
-
if (existsSync(dataDir)) {
|
|
45
|
-
rmSync(dataDir, { recursive: true });
|
|
46
|
-
log('Removed data/', 'green');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Remove any .db files in project root (legacy locations)
|
|
50
|
-
readdirSync(ROOT).filter(f => f.endsWith('.db') || f.endsWith('.db-wal') || f.endsWith('.db-shm')).forEach(f => {
|
|
51
|
-
unlinkSync(join(ROOT, f));
|
|
52
|
-
log(`Removed ${f}`, 'green');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Remove node_modules
|
|
56
|
-
const nodeModules = join(ROOT, 'node_modules');
|
|
57
|
-
if (existsSync(nodeModules)) { rmSync(nodeModules, { recursive: true }); log('Removed node_modules/', 'green'); }
|
|
58
|
-
|
|
59
|
-
// Remove package-lock.json
|
|
60
|
-
const lockFile = join(ROOT, 'package-lock.json');
|
|
61
|
-
if (existsSync(lockFile)) { unlinkSync(lockFile); log('Removed package-lock.json', 'green'); }
|
|
62
|
-
|
|
63
|
-
log('Clean complete', 'green');
|