warnect 0.1.0__tar.gz → 0.2.0__tar.gz
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.
- warnect-0.2.0/LICENSE +21 -0
- warnect-0.2.0/PKG-INFO +148 -0
- warnect-0.2.0/README.md +123 -0
- warnect-0.2.0/pyproject.toml +38 -0
- warnect-0.2.0/tests/test_client.py +75 -0
- warnect-0.2.0/warnect/__init__.py +13 -0
- warnect-0.2.0/warnect/bridge/lib/protocol.js +24 -0
- warnect-0.2.0/warnect/bridge/lib/validators.js +31 -0
- warnect-0.2.0/warnect/bridge/package-lock.json +1340 -0
- {warnect-0.1.0 → warnect-0.2.0}/warnect/bridge/package.json +4 -1
- warnect-0.2.0/warnect/bridge/server.js +269 -0
- warnect-0.2.0/warnect/client.py +315 -0
- warnect-0.2.0/warnect/exceptions.py +19 -0
- warnect-0.2.0/warnect/py.typed +0 -0
- warnect-0.2.0/warnect.egg-info/PKG-INFO +148 -0
- warnect-0.2.0/warnect.egg-info/SOURCES.txt +18 -0
- warnect-0.2.0/warnect.egg-info/requires.txt +6 -0
- warnect-0.1.0/PKG-INFO +0 -66
- warnect-0.1.0/README.md +0 -53
- warnect-0.1.0/pyproject.toml +0 -27
- warnect-0.1.0/warnect/__init__.py +0 -6
- warnect-0.1.0/warnect/bridge/server.js +0 -55
- warnect-0.1.0/warnect/client.py +0 -94
- warnect-0.1.0/warnect.egg-info/PKG-INFO +0 -66
- warnect-0.1.0/warnect.egg-info/SOURCES.txt +0 -10
- {warnect-0.1.0 → warnect-0.2.0}/setup.cfg +0 -0
- {warnect-0.1.0 → warnect-0.2.0}/warnect.egg-info/dependency_links.txt +0 -0
- {warnect-0.1.0 → warnect-0.2.0}/warnect.egg-info/top_level.txt +0 -0
warnect-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arsya fils
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
warnect-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: warnect
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python interface to arsya-baileys-open (WhatsApp Web) via a local Node.js bridge
|
|
5
|
+
Author: Arsya
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ziole-visa/warnect
|
|
8
|
+
Project-URL: Repository, https://github.com/ziole-visa/warnect
|
|
9
|
+
Project-URL: Issues, https://github.com/ziole-visa/warnect/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/ziole-visa/warnect/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: whatsapp,baileys,warnect,automation,bot
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
|
|
22
|
+
Requires-Dist: build; extra == "dev"
|
|
23
|
+
Requires-Dist: twine; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# warnect (Python)
|
|
27
|
+
|
|
28
|
+
Python client for WhatsApp Web multi-device, powered by **arsya-baileys-open**
|
|
29
|
+
through a local Node.js bridge.
|
|
30
|
+
|
|
31
|
+
- No browser, no Selenium.
|
|
32
|
+
- Bridge talks over stdio only — no TCP port is exposed.
|
|
33
|
+
- Session directory is created with `0700` permissions.
|
|
34
|
+
- Inputs validated, sends rate-limited, auto-reconnect with backoff.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Python >= 3.10
|
|
39
|
+
- Node.js >= 20 and npm
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install warnect
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Node dependencies are installed automatically on first run. To install manually:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -c "import warnect, pathlib; print(pathlib.Path(warnect.__file__).parent / 'bridge')"
|
|
51
|
+
cd <that-folder> && npm ci --omit=dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from warnect import BaileysClient
|
|
58
|
+
|
|
59
|
+
client = BaileysClient(auth_dir="auth")
|
|
60
|
+
|
|
61
|
+
def on_qr(qr):
|
|
62
|
+
print("Scan QR:", qr)
|
|
63
|
+
|
|
64
|
+
def on_ready(data):
|
|
65
|
+
print("Connected as:", data.get("user"))
|
|
66
|
+
|
|
67
|
+
def on_message(msg):
|
|
68
|
+
print("Pesan:", msg.get("text"))
|
|
69
|
+
if msg.get("text") == "ping":
|
|
70
|
+
client.send_text(msg["chat"], "pong")
|
|
71
|
+
|
|
72
|
+
client.on("qr", on_qr)
|
|
73
|
+
client.on("ready", on_ready)
|
|
74
|
+
client.on("message", on_message)
|
|
75
|
+
client.start()
|
|
76
|
+
|
|
77
|
+
input("Bot jalan. Tekan Enter untuk stop...\n")
|
|
78
|
+
client.stop()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Events
|
|
82
|
+
|
|
83
|
+
| Event | Data |
|
|
84
|
+
| ------------ | --------------------------------------------- |
|
|
85
|
+
| `qr` | QR string (render/save it yourself) |
|
|
86
|
+
| `connection` | `{ "status": "connecting" \| "open" \| "close" }` |
|
|
87
|
+
| `ready` | `{ "user": {...} }` |
|
|
88
|
+
| `message` | normalized message object |
|
|
89
|
+
| `error` | `{ "code": "...", "message": "..." }` |
|
|
90
|
+
| `reconnect` | `{ "attempt": n, "delay": ms }` |
|
|
91
|
+
|
|
92
|
+
## Message object
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
{
|
|
96
|
+
"id": "...",
|
|
97
|
+
"chat": "123@s.whatsapp.net",
|
|
98
|
+
"sender": "...",
|
|
99
|
+
"fromMe": False,
|
|
100
|
+
"isGroup": False,
|
|
101
|
+
"type": "conversation",
|
|
102
|
+
"text": "halo",
|
|
103
|
+
"pushName": "Budi",
|
|
104
|
+
"timestamp": 1730000000,
|
|
105
|
+
"hasMedia": False,
|
|
106
|
+
"fileName": None,
|
|
107
|
+
"mimetype": None,
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API
|
|
112
|
+
|
|
113
|
+
| Method | Description |
|
|
114
|
+
| ------ | ----------- |
|
|
115
|
+
| `start(wait=True)` | Start bridge and socket |
|
|
116
|
+
| `ping()` | Health check |
|
|
117
|
+
| `send_text(jid, text)` | Send text |
|
|
118
|
+
| `send_image(jid, url=..., path=..., caption=...)` | Send image |
|
|
119
|
+
| `send_document(jid, path, filename=..., mimetype=..., caption=...)` | Send document |
|
|
120
|
+
| `get_groups(limit=100)` | List groups |
|
|
121
|
+
| `get_profile_picture(jid, type="image")` | Profile picture URL |
|
|
122
|
+
| `is_on_whatsapp(number)` | Check number |
|
|
123
|
+
| `logout()` | Log out session |
|
|
124
|
+
| `stop()` | Stop bridge |
|
|
125
|
+
|
|
126
|
+
## Errors
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from warnect import BridgeError, BridgeTimeout, BridgeCrashed
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
client.send_text("123@s.whatsapp.net", "hi")
|
|
133
|
+
except BridgeTimeout:
|
|
134
|
+
...
|
|
135
|
+
except BridgeError as exc:
|
|
136
|
+
print(exc.code, exc.message)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Security notes
|
|
140
|
+
|
|
141
|
+
- The bridge uses local stdio; it does not open a network port.
|
|
142
|
+
- Node dependencies are pinned in `package-lock.json` and installed with `npm ci`.
|
|
143
|
+
- Keep `auth_dir` private; it contains WhatsApp session credentials.
|
|
144
|
+
- Use `min_send_interval` (default `0.5`s) to avoid spamming.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
warnect-0.2.0/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# warnect (Python)
|
|
2
|
+
|
|
3
|
+
Python client for WhatsApp Web multi-device, powered by **arsya-baileys-open**
|
|
4
|
+
through a local Node.js bridge.
|
|
5
|
+
|
|
6
|
+
- No browser, no Selenium.
|
|
7
|
+
- Bridge talks over stdio only — no TCP port is exposed.
|
|
8
|
+
- Session directory is created with `0700` permissions.
|
|
9
|
+
- Inputs validated, sends rate-limited, auto-reconnect with backoff.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Python >= 3.10
|
|
14
|
+
- Node.js >= 20 and npm
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install warnect
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Node dependencies are installed automatically on first run. To install manually:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python -c "import warnect, pathlib; print(pathlib.Path(warnect.__file__).parent / 'bridge')"
|
|
26
|
+
cd <that-folder> && npm ci --omit=dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from warnect import BaileysClient
|
|
33
|
+
|
|
34
|
+
client = BaileysClient(auth_dir="auth")
|
|
35
|
+
|
|
36
|
+
def on_qr(qr):
|
|
37
|
+
print("Scan QR:", qr)
|
|
38
|
+
|
|
39
|
+
def on_ready(data):
|
|
40
|
+
print("Connected as:", data.get("user"))
|
|
41
|
+
|
|
42
|
+
def on_message(msg):
|
|
43
|
+
print("Pesan:", msg.get("text"))
|
|
44
|
+
if msg.get("text") == "ping":
|
|
45
|
+
client.send_text(msg["chat"], "pong")
|
|
46
|
+
|
|
47
|
+
client.on("qr", on_qr)
|
|
48
|
+
client.on("ready", on_ready)
|
|
49
|
+
client.on("message", on_message)
|
|
50
|
+
client.start()
|
|
51
|
+
|
|
52
|
+
input("Bot jalan. Tekan Enter untuk stop...\n")
|
|
53
|
+
client.stop()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Events
|
|
57
|
+
|
|
58
|
+
| Event | Data |
|
|
59
|
+
| ------------ | --------------------------------------------- |
|
|
60
|
+
| `qr` | QR string (render/save it yourself) |
|
|
61
|
+
| `connection` | `{ "status": "connecting" \| "open" \| "close" }` |
|
|
62
|
+
| `ready` | `{ "user": {...} }` |
|
|
63
|
+
| `message` | normalized message object |
|
|
64
|
+
| `error` | `{ "code": "...", "message": "..." }` |
|
|
65
|
+
| `reconnect` | `{ "attempt": n, "delay": ms }` |
|
|
66
|
+
|
|
67
|
+
## Message object
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
{
|
|
71
|
+
"id": "...",
|
|
72
|
+
"chat": "123@s.whatsapp.net",
|
|
73
|
+
"sender": "...",
|
|
74
|
+
"fromMe": False,
|
|
75
|
+
"isGroup": False,
|
|
76
|
+
"type": "conversation",
|
|
77
|
+
"text": "halo",
|
|
78
|
+
"pushName": "Budi",
|
|
79
|
+
"timestamp": 1730000000,
|
|
80
|
+
"hasMedia": False,
|
|
81
|
+
"fileName": None,
|
|
82
|
+
"mimetype": None,
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
| Method | Description |
|
|
89
|
+
| ------ | ----------- |
|
|
90
|
+
| `start(wait=True)` | Start bridge and socket |
|
|
91
|
+
| `ping()` | Health check |
|
|
92
|
+
| `send_text(jid, text)` | Send text |
|
|
93
|
+
| `send_image(jid, url=..., path=..., caption=...)` | Send image |
|
|
94
|
+
| `send_document(jid, path, filename=..., mimetype=..., caption=...)` | Send document |
|
|
95
|
+
| `get_groups(limit=100)` | List groups |
|
|
96
|
+
| `get_profile_picture(jid, type="image")` | Profile picture URL |
|
|
97
|
+
| `is_on_whatsapp(number)` | Check number |
|
|
98
|
+
| `logout()` | Log out session |
|
|
99
|
+
| `stop()` | Stop bridge |
|
|
100
|
+
|
|
101
|
+
## Errors
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from warnect import BridgeError, BridgeTimeout, BridgeCrashed
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
client.send_text("123@s.whatsapp.net", "hi")
|
|
108
|
+
except BridgeTimeout:
|
|
109
|
+
...
|
|
110
|
+
except BridgeError as exc:
|
|
111
|
+
print(exc.code, exc.message)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Security notes
|
|
115
|
+
|
|
116
|
+
- The bridge uses local stdio; it does not open a network port.
|
|
117
|
+
- Node dependencies are pinned in `package-lock.json` and installed with `npm ci`.
|
|
118
|
+
- Keep `auth_dir` private; it contains WhatsApp session credentials.
|
|
119
|
+
- Use `min_send_interval` (default `0.5`s) to avoid spamming.
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "warnect"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Python interface to arsya-baileys-open (WhatsApp Web) via a local Node.js bridge"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Arsya" }]
|
|
13
|
+
keywords = ["whatsapp", "baileys", "warnect", "automation", "bot"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/ziole-visa/warnect"
|
|
23
|
+
Repository = "https://github.com/ziole-visa/warnect"
|
|
24
|
+
Issues = "https://github.com/ziole-visa/warnect/issues"
|
|
25
|
+
Changelog = "https://github.com/ziole-visa/warnect/blob/main/CHANGELOG.md"
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=8.0", "pytest-timeout>=2.3", "build", "twine"]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
packages = ["warnect"]
|
|
32
|
+
include-package-data = false
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
warnect = ["bridge/server.js", "bridge/package.json", "bridge/package-lock.json", "bridge/lib/*.js", "py.typed"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from warnect import BaileysClient, BridgeError, BridgeTimeout
|
|
8
|
+
|
|
9
|
+
FAKE = str(Path(__file__).resolve().parent / "fake_bridge.js")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture()
|
|
13
|
+
def client(tmp_path):
|
|
14
|
+
c = BaileysClient(
|
|
15
|
+
auth_dir=str(tmp_path / "auth"),
|
|
16
|
+
bridge_script=FAKE,
|
|
17
|
+
auto_install=False,
|
|
18
|
+
min_send_interval=0.0,
|
|
19
|
+
)
|
|
20
|
+
yield c
|
|
21
|
+
c.stop()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_start_and_ping(client):
|
|
25
|
+
data = client.start()
|
|
26
|
+
assert data["status"] == "starting"
|
|
27
|
+
assert client.ping()["pong"] is True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_send_text_returns_response(client):
|
|
31
|
+
client.start()
|
|
32
|
+
assert client.send_text("123@s.whatsapp.net", "halo")["sent"] is True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_event_delivery(client):
|
|
36
|
+
got = []
|
|
37
|
+
event = threading.Event()
|
|
38
|
+
|
|
39
|
+
def on_message(msg):
|
|
40
|
+
got.append(msg)
|
|
41
|
+
event.set()
|
|
42
|
+
|
|
43
|
+
client.on("message", on_message)
|
|
44
|
+
client.start()
|
|
45
|
+
client.send_text("123@s.whatsapp.net", "hai")
|
|
46
|
+
assert event.wait(2.0)
|
|
47
|
+
assert got[0]["text"] == "hai"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_unknown_command_raises(client):
|
|
51
|
+
client.start()
|
|
52
|
+
with pytest.raises(BridgeError) as exc:
|
|
53
|
+
client.request("nope")
|
|
54
|
+
assert exc.value.code == "unknown_command"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_timeout(client):
|
|
58
|
+
client.start()
|
|
59
|
+
with pytest.raises(BridgeTimeout):
|
|
60
|
+
client.request("slow", timeout=0.2)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_stop_then_start_again(tmp_path):
|
|
64
|
+
c = BaileysClient(
|
|
65
|
+
auth_dir=str(tmp_path / "auth"),
|
|
66
|
+
bridge_script=FAKE,
|
|
67
|
+
auto_install=False,
|
|
68
|
+
min_send_interval=0.0,
|
|
69
|
+
)
|
|
70
|
+
c.start()
|
|
71
|
+
assert c.ping()["pong"] is True
|
|
72
|
+
c.stop()
|
|
73
|
+
c.start()
|
|
74
|
+
assert c.ping()["pong"] is True
|
|
75
|
+
c.stop()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""warnect: Python interface to arsya-baileys-open via a local Node bridge."""
|
|
2
|
+
|
|
3
|
+
from .client import BaileysClient
|
|
4
|
+
from .exceptions import BridgeCrashed, BridgeError, BridgeTimeout, WarnectError
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"BaileysClient",
|
|
8
|
+
"WarnectError",
|
|
9
|
+
"BridgeError",
|
|
10
|
+
"BridgeTimeout",
|
|
11
|
+
"BridgeCrashed",
|
|
12
|
+
]
|
|
13
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function createEmitter(writeLine) {
|
|
2
|
+
return function emit(event, data = null) {
|
|
3
|
+
writeLine(JSON.stringify({ type: 'event', event, data }));
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function respond(writeLine, id, data = null) {
|
|
8
|
+
writeLine(JSON.stringify({ type: 'response', id, ok: true, data }));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function respondError(writeLine, id, code, message) {
|
|
12
|
+
writeLine(JSON.stringify({ type: 'response', id, ok: false, error: { code, message } }));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function parseLine(line) {
|
|
16
|
+
const trimmed = String(line || '').trim();
|
|
17
|
+
if (!trimmed) return null;
|
|
18
|
+
const obj = JSON.parse(trimmed);
|
|
19
|
+
if (!obj || typeof obj !== 'object') throw new Error('payload must be an object');
|
|
20
|
+
if (obj.type !== 'request') throw new Error('unsupported message type');
|
|
21
|
+
if (obj.id === undefined || obj.id === null) throw new Error('request id is required');
|
|
22
|
+
if (typeof obj.cmd !== 'string' || !obj.cmd) throw new Error('request cmd is required');
|
|
23
|
+
return obj;
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const JID_RE = /^\d+(-[\d]+)?@(s\.whatsapp\.net|g\.us|broadcast|lid|newsletter)$/;
|
|
2
|
+
|
|
3
|
+
export function validateJid(jid) {
|
|
4
|
+
if (typeof jid !== 'string' || !JID_RE.test(jid)) {
|
|
5
|
+
throw new Error(`invalid jid: ${jid}`);
|
|
6
|
+
}
|
|
7
|
+
return jid;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function validateText(text, maxLength = 65536) {
|
|
11
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
12
|
+
throw new Error('text must be a non-empty string');
|
|
13
|
+
}
|
|
14
|
+
if (text.length > maxLength) {
|
|
15
|
+
throw new Error(`text too long (max ${maxLength})`);
|
|
16
|
+
}
|
|
17
|
+
return text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function validateLimit(value, def = 100, max = 1000) {
|
|
21
|
+
const n = Number(value ?? def);
|
|
22
|
+
if (!Number.isFinite(n) || n < 1 || n > max) {
|
|
23
|
+
throw new Error(`limit must be between 1 and ${max}`);
|
|
24
|
+
}
|
|
25
|
+
return Math.floor(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseBool(value, def = false) {
|
|
29
|
+
if (value === undefined || value === null) return def;
|
|
30
|
+
return value === true || value === 'true' || value === 1 || value === '1';
|
|
31
|
+
}
|