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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # WhatsApp WEB RPC
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/whatsapp-rpc.svg)](https://www.npmjs.com/package/whatsapp-rpc)
4
+ [![PyPI version](https://img.shields.io/pypi/v/whatsapp-rpc.svg)](https://pypi.org/project/whatsapp-rpc/)
4
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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, filename, image_data as base64 PNG) |
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, filename, image_data) |
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+
@@ -2,6 +2,6 @@ environment: "development"
2
2
  log_level: 4
3
3
  server:
4
4
  port: 9400
5
- host: "0.0.0.0"
5
+ host: "127.0.0.1"
6
6
  database:
7
7
  path: "data/whatsapp.db"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-rpc",
3
- "version": "0.0.6",
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
- "clean": "node scripts/clean.cjs"
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 API_PORT = 9400;
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(foreground = false) {
47
- if (await portUp(API_PORT)) { log(`API already running on port ${API_PORT}`, 'yellow'); return; }
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:${API_PORT}/ws/rpc`, 'green');
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(API_PORT)) log(`API started: ws://localhost:${API_PORT}/ws/rpc`, 'green');
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
- async function stop() { await kill(API_PORT, 'API'); }
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 a = await portUp(API_PORT);
82
- log(`API (${API_PORT}): ${a ? 'UP' : 'DOWN'}`, a ? 'green' : 'red');
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
- program.name('whatsapp-rpc').version('1.0.0');
114
- program.command('start').description('Start API server').action(start);
115
- program.command('stop').description('Stop API server').action(stop);
116
- program.command('restart').description('Restart API server').action(async () => { await stop(); await sleep(1000); await start(); });
117
- program.command('status').description('Show server status').action(status);
118
- program.command('api').description('Start API server').option('-f, --foreground', 'Run in foreground').action((opts) => api(opts.foreground));
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');