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 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
@@ -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
+ }