@lofa199419/waha-v2 2.1.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/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # `@lofa199419/waha-v2`
2
+
3
+ OpenClaw WAHA v2 channel plugin with a packaged local CLI wrapper for WAHA session and messaging operations.
4
+
5
+ ## What ships
6
+
7
+ - OpenClaw plugin files: `index.ts`, `src/`, `openclaw.plugin.json`
8
+ - Skill docs: `skills/waha-v2/SKILL.md`
9
+ - Local CLI wrappers:
10
+ - `bin/waha-cli`
11
+ - `bin/wa`
12
+ - `bin/wa-adv`
13
+ - `bin/waha-advanced-entrypoint`
14
+ - `bin/waha_cli.py`
15
+
16
+ ## Install into an OpenClaw state dir
17
+
18
+ ```bash
19
+ npm install @lofa199419/waha-v2
20
+ node node_modules/@lofa199419/waha-v2/scripts/install-openclaw-extension.mjs --state-dir /data/.openclaw --force
21
+ ```
22
+
23
+ Or with `OPENCLAW_STATE_DIR`:
24
+
25
+ ```bash
26
+ OPENCLAW_STATE_DIR=/data/.openclaw node node_modules/@lofa199419/waha-v2/scripts/install-openclaw-extension.mjs --force
27
+ ```
28
+
29
+ ## Runtime configuration
30
+
31
+ The plugin package does not ship secrets. Provide WAHA configuration through your container or startup flow and render:
32
+
33
+ ```bash
34
+ /data/.openclaw/extensions/waha-v2/bin/.env
35
+ ```
36
+
37
+ Expected values:
38
+
39
+ ```dotenv
40
+ WAHA_URL=https://your-waha-host
41
+ WAHA_API_KEY=your-api-key
42
+ WAHA_SESSION_DEFAULT=power
43
+ ```
44
+
45
+ ## Publish checklist
46
+
47
+ ```bash
48
+ npm pack --dry-run
49
+ npm publish --access public
50
+ ```
package/bin/wa ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ DIR="$(cd "$(dirname "$0")" && pwd)"
4
+ if [[ -f "$DIR/.env" ]]; then
5
+ set -a
6
+ source "$DIR/.env"
7
+ set +a
8
+ fi
9
+ ARGS=("$@")
10
+ HAS_SESSION=0
11
+ for ((i=0; i<${#ARGS[@]}; i++)); do
12
+ if [[ "${ARGS[$i]}" == "--session" ]]; then
13
+ HAS_SESSION=1
14
+ break
15
+ fi
16
+ done
17
+ if [[ $HAS_SESSION -eq 0 && -n "${WAHA_SESSION_DEFAULT:-}" ]]; then
18
+ ARGS+=(--session "$WAHA_SESSION_DEFAULT")
19
+ fi
20
+ exec "$DIR/waha-cli" "${ARGS[@]}"
package/bin/wa-adv ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ DIR="$(cd "$(dirname "$0")" && pwd)"
4
+ if [[ -f "$DIR/.env" ]]; then
5
+ set -a
6
+ source "$DIR/.env"
7
+ set +a
8
+ fi
9
+ exec "$DIR/waha-advanced-entrypoint" "$@"
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ ENTRYPOINT="$DIR/waha_cli.py"
6
+
7
+ if [[ ! -f "$ENTRYPOINT" ]]; then
8
+ echo "waha-v2: missing WAHA CLI shim at $ENTRYPOINT" >&2
9
+ exit 1
10
+ fi
11
+
12
+ exec python3 "$ENTRYPOINT" "$@"
package/bin/waha-cli ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ DIR="$(cd "$(dirname "$0")" && pwd)"
4
+ ENV_FILE="$DIR/.env"
5
+ if [ -f "$ENV_FILE" ]; then
6
+ set -a
7
+ # shellcheck disable=SC1090
8
+ . "$ENV_FILE"
9
+ set +a
10
+ fi
11
+ exec "$DIR/waha-advanced-entrypoint" "$@"
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import json
4
+ import os
5
+ import pathlib
6
+ import sys
7
+ import time
8
+ import urllib.error
9
+ import urllib.parse
10
+ import urllib.request
11
+
12
+ VERSION = "3.0.0"
13
+ SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
14
+ OPENCLAW_CHANNEL_ID = "waha-v2"
15
+ DEFAULT_CHARS_PER_SECOND = 12
16
+ DEFAULT_MAX_CHUNK_LENGTH = 1500
17
+ SEEN_DELAY_MS = 3000
18
+ MIN_DELAY_MS = 600
19
+ MAX_DELAY_MS = 8000
20
+
21
+
22
+ def env(name: str, default: str | None = None) -> str | None:
23
+ return os.environ.get(name, default)
24
+
25
+
26
+ def state_dir() -> pathlib.Path:
27
+ configured = env("OPENCLAW_STATE_DIR")
28
+ if configured:
29
+ return pathlib.Path(configured)
30
+ home = pathlib.Path(env("HOME", str(pathlib.Path.home()))).expanduser()
31
+ return home / ".openclaw"
32
+
33
+
34
+ def parse_dotenv(path: pathlib.Path) -> dict[str, str]:
35
+ values: dict[str, str] = {}
36
+ if not path.exists():
37
+ return values
38
+ for raw_line in path.read_text().splitlines():
39
+ line = raw_line.strip()
40
+ if not line or line.startswith("#") or "=" not in line:
41
+ continue
42
+ key, value = line.split("=", 1)
43
+ values[key.strip()] = value.strip().strip("'").strip('"')
44
+ return values
45
+
46
+
47
+ def load_openclaw_channel_config() -> dict:
48
+ config_path = state_dir() / "openclaw.json"
49
+ if not config_path.exists():
50
+ return {}
51
+ try:
52
+ data = json.loads(config_path.read_text())
53
+ except Exception:
54
+ return {}
55
+ channels = data.get("channels")
56
+ if not isinstance(channels, dict):
57
+ return {}
58
+ channel = channels.get(OPENCLAW_CHANNEL_ID)
59
+ return channel if isinstance(channel, dict) else {}
60
+
61
+
62
+ def resolve_account_config(channel_config: dict, session_name: str | None) -> dict:
63
+ accounts = channel_config.get("accounts")
64
+ if not isinstance(accounts, dict):
65
+ return {}
66
+ wanted = (session_name or "").strip().lower()
67
+ if not wanted:
68
+ return {}
69
+ exact = accounts.get(session_name)
70
+ if isinstance(exact, dict):
71
+ return exact
72
+ for account_id, config in accounts.items():
73
+ if not isinstance(config, dict):
74
+ continue
75
+ account_name = str(config.get("name", account_id)).strip().lower()
76
+ account_session = str(config.get("session", "")).strip().lower()
77
+ if wanted in {str(account_id).strip().lower(), account_name, account_session}:
78
+ return config
79
+ return {}
80
+
81
+
82
+ def resolved_config(session_name: str | None = None) -> dict[str, str]:
83
+ dotenv = parse_dotenv(SCRIPT_DIR / ".env")
84
+ channel = load_openclaw_channel_config()
85
+ account = resolve_account_config(channel, session_name)
86
+
87
+ session_value = (
88
+ env("WAHA_SESSION_DEFAULT")
89
+ or env("WAHA_DEFAULT_SESSION")
90
+ or str(account.get("session", "")).strip()
91
+ or str(channel.get("session", "")).strip()
92
+ or dotenv.get("WAHA_SESSION_DEFAULT")
93
+ or dotenv.get("WAHA_DEFAULT_SESSION")
94
+ or "default"
95
+ )
96
+ return {
97
+ "WAHA_URL": (
98
+ str(account.get("baseUrl", "")).strip()
99
+ or str(channel.get("baseUrl", "")).strip()
100
+ or env("WAHA_URL")
101
+ or dotenv.get("WAHA_URL", "")
102
+ ),
103
+ "WAHA_API_KEY": (
104
+ str(account.get("apiKey", "")).strip()
105
+ or str(channel.get("apiKey", "")).strip()
106
+ or env("WAHA_API_KEY")
107
+ or dotenv.get("WAHA_API_KEY", "")
108
+ ),
109
+ "WAHA_SESSION_DEFAULT": session_value,
110
+ }
111
+
112
+
113
+ def resolved_typing_config(session_name: str | None = None) -> dict[str, object]:
114
+ channel = load_openclaw_channel_config()
115
+ account = resolve_account_config(channel, session_name)
116
+ typing: dict = channel.get("typing") if isinstance(channel.get("typing"), dict) else {}
117
+ account_typing: dict = account.get("typing") if isinstance(account.get("typing"), dict) else {}
118
+
119
+ def pick(name: str, default):
120
+ if name in account_typing:
121
+ return account_typing[name]
122
+ if name in typing:
123
+ return typing[name]
124
+ return default
125
+
126
+ return {
127
+ "enabled": pick("enabled", True) is not False,
128
+ "chunking": pick("chunking", True) is not False,
129
+ "charsPerSecond": int(pick("charsPerSecond", DEFAULT_CHARS_PER_SECOND)),
130
+ "maxChunkLength": int(pick("maxChunkLength", DEFAULT_MAX_CHUNK_LENGTH)),
131
+ "debug": pick("debug", False) is True,
132
+ }
133
+
134
+
135
+ def require_resolved(name: str, session_name: str | None = None) -> str:
136
+ value = resolved_config(session_name).get(name, "")
137
+ if not value:
138
+ if name == "WAHA_URL" or name == "WAHA_API_KEY":
139
+ print(
140
+ f"Missing required config: {name}. Checked channels.{OPENCLAW_CHANNEL_ID} in "
141
+ f"{state_dir() / 'openclaw.json'}, then environment, then {SCRIPT_DIR / '.env'}",
142
+ file=sys.stderr,
143
+ )
144
+ else:
145
+ print(f"Missing required config: {name}", file=sys.stderr)
146
+ sys.exit(2)
147
+ return value
148
+
149
+
150
+ def default_session() -> str:
151
+ return require_resolved("WAHA_SESSION_DEFAULT")
152
+
153
+
154
+ def normalize_base_url(value: str) -> str:
155
+ return value.rstrip("/")
156
+
157
+
158
+ def sleep_ms(delay_ms: int) -> None:
159
+ time.sleep(max(0, delay_ms) / 1000.0)
160
+
161
+
162
+ def calc_typing_delay_ms(text: str, chars_per_second: int) -> int:
163
+ raw = round((len(text) / max(chars_per_second, 1)) * 1000)
164
+ return max(MIN_DELAY_MS, min(MAX_DELAY_MS, raw))
165
+
166
+
167
+ def chunk_waha_message(text: str, max_length: int) -> list[str]:
168
+ chunks: list[str] = []
169
+ paragraphs = text.split("\n\n")
170
+ for para in paragraphs:
171
+ trimmed = para.strip()
172
+ if not trimmed:
173
+ continue
174
+ if len(trimmed) <= max_length:
175
+ chunks.append(trimmed)
176
+ continue
177
+ current = trimmed
178
+ while len(current) > max_length:
179
+ split_at = max(current.rfind("\n", 0, max_length), current.rfind(" ", 0, max_length))
180
+ if split_at <= 0:
181
+ split_at = max_length
182
+ part = current[:split_at].strip()
183
+ if part:
184
+ chunks.append(part)
185
+ current = current[split_at:].strip()
186
+ if current:
187
+ chunks.append(current)
188
+ if chunks:
189
+ return chunks
190
+ stripped = text.strip()
191
+ return [stripped] if stripped else []
192
+
193
+
194
+ def debug_log(enabled: bool, message: str) -> None:
195
+ if not enabled:
196
+ return
197
+ print(f"[waha-cli delivery] {time.strftime('%Y-%m-%dT%H:%M:%S%z')} {message}", file=sys.stderr)
198
+
199
+
200
+ def send_presence(endpoint: str, session: str, chat_id: str) -> None:
201
+ waha_request("POST", endpoint, {"session": session, "chatId": chat_id}, session_name=session)
202
+
203
+
204
+ def waha_request(method: str, path: str, body: dict | None = None, query: dict | None = None, session_name: str | None = None):
205
+ base_url = normalize_base_url(require_resolved("WAHA_URL", session_name))
206
+ api_key = require_resolved("WAHA_API_KEY", session_name)
207
+ url = f"{base_url}{path}"
208
+ if query:
209
+ filtered = {k: v for k, v in query.items() if v is not None}
210
+ if filtered:
211
+ url = f"{url}?{urllib.parse.urlencode(filtered)}"
212
+ data = None
213
+ headers = {
214
+ "X-Api-Key": api_key,
215
+ "User-Agent": f"waha-cli/{VERSION}",
216
+ "Accept": "application/json, text/plain;q=0.9, */*;q=0.8",
217
+ }
218
+ if body is not None:
219
+ data = json.dumps(body).encode("utf-8")
220
+ headers["Content-Type"] = "application/json"
221
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
222
+ try:
223
+ with urllib.request.urlopen(req) as resp:
224
+ payload = resp.read()
225
+ content_type = resp.headers.get("content-type", "")
226
+ if "application/json" in content_type:
227
+ return json.loads(payload.decode("utf-8"))
228
+ return payload.decode("utf-8")
229
+ except urllib.error.HTTPError as exc:
230
+ try:
231
+ error_body = exc.read().decode("utf-8")
232
+ parsed = json.loads(error_body)
233
+ message = parsed.get("message", error_body)
234
+ except Exception:
235
+ message = exc.reason
236
+ print(f"WAHA API error ({exc.code}): {message}", file=sys.stderr)
237
+ sys.exit(1)
238
+ except urllib.error.URLError as exc:
239
+ print(f"WAHA API request failed: {exc.reason}", file=sys.stderr)
240
+ sys.exit(1)
241
+
242
+
243
+ def print_json(data):
244
+ print(json.dumps(data, indent=2, ensure_ascii=False))
245
+
246
+
247
+ def print_help():
248
+ print(
249
+ f"""waha-cli v{VERSION}
250
+
251
+ Usage:
252
+ waha-cli --help
253
+ waha-cli --version
254
+ waha-cli <command> [options]
255
+
256
+ Session and Auth:
257
+ waha-list-sessions
258
+ waha-get-session --session NAME
259
+ waha-create-session --name NAME
260
+ waha-start-session --session NAME
261
+ waha-stop-session --session NAME
262
+ waha-restart-session --session NAME
263
+ waha-delete-session --session NAME
264
+ waha-logout-session --session NAME
265
+ waha-get-qr-code --session NAME
266
+ waha-request-pairing-code --session NAME --phone-number NUMBER
267
+ waha-check-auth-status --session NAME
268
+
269
+ Chats and Messages:
270
+ waha-get-chats [--session NAME] [--limit N] [--offset N]
271
+ waha-get-messages --chat-id ID [--session NAME] [--limit N] [--offset N]
272
+ waha-send-text --chat-id ID --text TEXT [--session NAME]
273
+
274
+ Optional env:
275
+ WAHA_SESSION_DEFAULT / WAHA_DEFAULT_SESSION
276
+ Fallback env:
277
+ WAHA_URL
278
+ WAHA_API_KEY
279
+
280
+ Default config source:
281
+ OPENCLAW_STATE_DIR/openclaw.json -> channels.waha-v2"""
282
+ )
283
+
284
+
285
+ def add_session_arg(parser):
286
+ parser.add_argument("--session", default=default_session())
287
+
288
+
289
+ def build_parser():
290
+ parser = argparse.ArgumentParser(add_help=False)
291
+ parser.add_argument("--help", action="store_true")
292
+ parser.add_argument("--version", action="store_true")
293
+ parser.add_argument("command", nargs="?")
294
+ parser.add_argument("rest", nargs=argparse.REMAINDER)
295
+ return parser
296
+
297
+
298
+ def cmd_list_sessions(_args):
299
+ print_json(waha_request("GET", "/api/sessions"))
300
+
301
+
302
+ def cmd_get_session(args):
303
+ print_json(waha_request("GET", f"/api/sessions/{urllib.parse.quote(args.session, safe='')}", session_name=args.session))
304
+
305
+
306
+ def cmd_create_session(args):
307
+ body = {"name": args.name}
308
+ print_json(waha_request("POST", "/api/sessions", body, session_name=args.name))
309
+
310
+
311
+ def cmd_simple_post(args, suffix):
312
+ print_json(
313
+ waha_request(
314
+ "POST",
315
+ f"/api/sessions/{urllib.parse.quote(args.session, safe='')}/{suffix}",
316
+ session_name=args.session,
317
+ )
318
+ )
319
+
320
+
321
+ def cmd_delete_session(args):
322
+ waha_request("DELETE", f"/api/sessions/{urllib.parse.quote(args.session, safe='')}", session_name=args.session)
323
+ print(f'Session "{args.session}" deleted successfully.')
324
+
325
+
326
+ def cmd_logout_session(args):
327
+ waha_request("POST", f"/api/sessions/{urllib.parse.quote(args.session, safe='')}/logout", session_name=args.session)
328
+ print(f'Session "{args.session}" logged out successfully.')
329
+
330
+
331
+ def cmd_get_qr_code(args):
332
+ qr = waha_request(
333
+ "GET",
334
+ f"/api/{urllib.parse.quote(args.session, safe='')}/auth/qr",
335
+ query={"format": "raw"},
336
+ session_name=args.session,
337
+ )
338
+ if isinstance(qr, dict) and "value" in qr:
339
+ qr = qr["value"]
340
+ print(f'QR Code value for session "{args.session}":')
341
+ print(qr)
342
+
343
+
344
+ def cmd_request_pairing_code(args):
345
+ result = waha_request(
346
+ "POST",
347
+ f"/api/{urllib.parse.quote(args.session, safe='')}/auth/request-code",
348
+ {"phoneNumber": args.phone_number},
349
+ session_name=args.session,
350
+ )
351
+ code = result.get("code", "<missing>")
352
+ print(
353
+ f'Pairing code requested for {args.phone_number} on session "{args.session}".\n'
354
+ f"Code: {code}\n"
355
+ "Enter this code in WhatsApp on your phone."
356
+ )
357
+
358
+
359
+ def cmd_check_auth_status(args):
360
+ info = waha_request("GET", f"/api/sessions/{urllib.parse.quote(args.session, safe='')}", session_name=args.session)
361
+ messages = {
362
+ "STOPPED": "Session is stopped. Start it first.",
363
+ "STARTING": "Session is starting up...",
364
+ "SCAN_QR_CODE": "Waiting for QR code scan. Use waha-get-qr-code or waha-request-pairing-code.",
365
+ "WORKING": "Session is authenticated and working.",
366
+ "FAILED": "Session has failed. Try restarting or re-authenticating.",
367
+ }
368
+ text = [
369
+ f"Session: {args.session}",
370
+ f"Status: {info.get('status', 'UNKNOWN')}",
371
+ messages.get(info.get("status"), f"Unknown status: {info.get('status')}"),
372
+ ]
373
+ me = info.get("me")
374
+ if isinstance(me, dict):
375
+ if me.get("id"):
376
+ text.append(f"Account ID: {me['id']}")
377
+ if me.get("pushName"):
378
+ text.append(f"Name: {me['pushName']}")
379
+ print("\n".join(text))
380
+
381
+
382
+ def cmd_get_chats(args):
383
+ print_json(
384
+ waha_request(
385
+ "GET",
386
+ f"/api/{urllib.parse.quote(args.session, safe='')}/chats",
387
+ query={"limit": args.limit, "offset": args.offset},
388
+ session_name=args.session,
389
+ )
390
+ )
391
+
392
+
393
+ def cmd_get_messages(args):
394
+ query = {"limit": args.limit, "offset": args.offset}
395
+ print_json(
396
+ waha_request(
397
+ "GET",
398
+ f"/api/{urllib.parse.quote(args.session, safe='')}/chats/{urllib.parse.quote(args.chat_id, safe='')}/messages",
399
+ query=query,
400
+ session_name=args.session,
401
+ )
402
+ )
403
+
404
+
405
+ def cmd_send_text(args):
406
+ typing_cfg = resolved_typing_config(args.session)
407
+ typing_enabled = bool(typing_cfg["enabled"])
408
+ chunking_enabled = bool(typing_cfg["chunking"])
409
+ chars_per_second = int(typing_cfg["charsPerSecond"])
410
+ max_chunk_length = int(typing_cfg["maxChunkLength"])
411
+ debug_enabled = bool(typing_cfg["debug"])
412
+
413
+ chunks = chunk_waha_message(args.text, max_chunk_length) if chunking_enabled else [args.text.strip()]
414
+ chunks = [chunk for chunk in chunks if chunk]
415
+ if not chunks:
416
+ print("Nothing to send.", file=sys.stderr)
417
+ sys.exit(1)
418
+
419
+ debug_log(debug_enabled, f"queued outbound send chat={args.chat_id} session={args.session}")
420
+ sleep_ms(SEEN_DELAY_MS)
421
+ waha_request(
422
+ "POST",
423
+ "/api/sendSeen",
424
+ {"session": args.session, "chatId": args.chat_id},
425
+ session_name=args.session,
426
+ )
427
+ debug_log(debug_enabled, f"marked seen after {SEEN_DELAY_MS}ms chat={args.chat_id}")
428
+
429
+ pre_output_typing_started = False
430
+ if typing_enabled:
431
+ send_presence("/api/startTyping", args.session, args.chat_id)
432
+ pre_output_typing_started = True
433
+ debug_log(debug_enabled, f"pre-output started typing chat={args.chat_id}")
434
+
435
+ debug_log(
436
+ debug_enabled,
437
+ f"output ready chat={args.chat_id} chunking={chunking_enabled} chunks={len(chunks)} totalLen={len(args.text)}",
438
+ )
439
+
440
+ message_ids: list[str] = []
441
+ if pre_output_typing_started:
442
+ send_presence("/api/stopTyping", args.session, args.chat_id)
443
+ debug_log(debug_enabled, f"pre-output stopped typing chat={args.chat_id}")
444
+
445
+ if len(chunks) == 1:
446
+ result = waha_request(
447
+ "POST",
448
+ "/api/sendText",
449
+ {"session": args.session, "chatId": args.chat_id, "text": chunks[0]},
450
+ session_name=args.session,
451
+ )
452
+ message_ids.append(str(result.get("id", "<missing>")))
453
+ debug_log(debug_enabled, f"sent single reply chat={args.chat_id} len={len(chunks[0])}")
454
+ else:
455
+ for i, chunk in enumerate(chunks):
456
+ if i > 0 and typing_enabled:
457
+ send_presence("/api/startTyping", args.session, args.chat_id)
458
+ debug_log(debug_enabled, f"chunk {i + 1}/{len(chunks)} started typing chat={args.chat_id}")
459
+ sleep_ms(calc_typing_delay_ms(chunk, chars_per_second))
460
+ send_presence("/api/stopTyping", args.session, args.chat_id)
461
+ debug_log(debug_enabled, f"chunk {i + 1}/{len(chunks)} stopped typing chat={args.chat_id}")
462
+
463
+ result = waha_request(
464
+ "POST",
465
+ "/api/sendText",
466
+ {"session": args.session, "chatId": args.chat_id, "text": chunk},
467
+ session_name=args.session,
468
+ )
469
+ message_ids.append(str(result.get("id", "<missing>")))
470
+ debug_log(debug_enabled, f"sent chunk {i + 1}/{len(chunks)} chat={args.chat_id} len={len(chunk)}")
471
+
472
+ print("Message sent successfully.")
473
+ for index, message_id in enumerate(message_ids, start=1):
474
+ if len(message_ids) == 1:
475
+ print(f"Message ID: {message_id}")
476
+ else:
477
+ print(f"Chunk {index} Message ID: {message_id}")
478
+
479
+
480
+ COMMANDS = {
481
+ "waha-list-sessions": ("List sessions", cmd_list_sessions),
482
+ "waha-get-session": ("Get session", cmd_get_session),
483
+ "waha-create-session": ("Create session", cmd_create_session),
484
+ "waha-start-session": ("Start session", lambda args: cmd_simple_post(args, "start")),
485
+ "waha-stop-session": ("Stop session", lambda args: cmd_simple_post(args, "stop")),
486
+ "waha-restart-session": ("Restart session", lambda args: cmd_simple_post(args, "restart")),
487
+ "waha-delete-session": ("Delete session", cmd_delete_session),
488
+ "waha-logout-session": ("Logout session", cmd_logout_session),
489
+ "waha-get-qr-code": ("Get QR code", cmd_get_qr_code),
490
+ "waha-request-pairing-code": ("Request pairing code", cmd_request_pairing_code),
491
+ "waha-check-auth-status": ("Check auth status", cmd_check_auth_status),
492
+ "waha-get-chats": ("Get chats", cmd_get_chats),
493
+ "waha-list-chats": ("List chats", cmd_get_chats),
494
+ "waha-get-messages": ("Get messages", cmd_get_messages),
495
+ "waha-send-text": ("Send text", cmd_send_text),
496
+ }
497
+
498
+
499
+ def command_parser(command: str):
500
+ parser = argparse.ArgumentParser(prog=f"waha-cli {command}")
501
+ if command in {"waha-get-session", "waha-start-session", "waha-stop-session", "waha-restart-session", "waha-delete-session", "waha-logout-session", "waha-get-qr-code", "waha-check-auth-status"}:
502
+ add_session_arg(parser)
503
+ elif command == "waha-create-session":
504
+ parser.add_argument("--name", required=True)
505
+ elif command == "waha-request-pairing-code":
506
+ add_session_arg(parser)
507
+ parser.add_argument("--phone-number", required=True)
508
+ elif command in {"waha-get-chats", "waha-list-chats"}:
509
+ add_session_arg(parser)
510
+ parser.add_argument("--limit", type=int, default=50)
511
+ parser.add_argument("--offset", type=int, default=0)
512
+ elif command == "waha-get-messages":
513
+ add_session_arg(parser)
514
+ parser.add_argument("--chat-id", required=True)
515
+ parser.add_argument("--limit", type=int, default=20)
516
+ parser.add_argument("--offset", type=int, default=0)
517
+ elif command == "waha-send-text":
518
+ add_session_arg(parser)
519
+ parser.add_argument("--chat-id", required=True)
520
+ parser.add_argument("--text", required=True)
521
+ return parser
522
+
523
+
524
+ def main():
525
+ parser = build_parser()
526
+ args = parser.parse_args()
527
+
528
+ if args.help or not args.command:
529
+ print_help()
530
+ return 0
531
+ if args.version:
532
+ print(VERSION)
533
+ return 0
534
+
535
+ command = args.command.replace("_", "-")
536
+ handler_entry = COMMANDS.get(command)
537
+ if handler_entry is None:
538
+ print(f"Unknown command: {args.command}", file=sys.stderr)
539
+ print_help()
540
+ return 2
541
+
542
+ cmd_parser = command_parser(command)
543
+ cmd_args = cmd_parser.parse_args(args.rest)
544
+ handler_entry[1](cmd_args)
545
+ return 0
546
+
547
+
548
+ if __name__ == "__main__":
549
+ sys.exit(main())