arplyx 0.1.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.
- arplyx-0.1.0/PKG-INFO +105 -0
- arplyx-0.1.0/README.md +89 -0
- arplyx-0.1.0/pyproject.toml +26 -0
- arplyx-0.1.0/setup.cfg +4 -0
- arplyx-0.1.0/src/arplyx/__init__.py +20 -0
- arplyx-0.1.0/src/arplyx/client.py +157 -0
- arplyx-0.1.0/src/arplyx/errors.py +29 -0
- arplyx-0.1.0/src/arplyx/types.py +92 -0
- arplyx-0.1.0/src/arplyx.egg-info/PKG-INFO +105 -0
- arplyx-0.1.0/src/arplyx.egg-info/SOURCES.txt +11 -0
- arplyx-0.1.0/src/arplyx.egg-info/dependency_links.txt +1 -0
- arplyx-0.1.0/src/arplyx.egg-info/requires.txt +1 -0
- arplyx-0.1.0/src/arplyx.egg-info/top_level.txt +1 -0
arplyx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arplyx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK oficial de Arplyx: enviá mensajes y broadcasts de WhatsApp desde Python.
|
|
5
|
+
Author-email: Arplyx <info@arplyx.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://docs.arplyx.com/api/
|
|
8
|
+
Project-URL: Documentation, https://docs.arplyx.com/api/
|
|
9
|
+
Keywords: arplyx,whatsapp,whatsapp-api,sdk,mensajeria,broadcasts
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: requests>=2.25
|
|
16
|
+
|
|
17
|
+
# arplyx (Python SDK)
|
|
18
|
+
|
|
19
|
+
SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.
|
|
20
|
+
|
|
21
|
+
## Instalación
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install arplyx
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requiere Python 3.8+.
|
|
28
|
+
|
|
29
|
+
## Uso
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from arplyx import Arplyx, ArplyxError
|
|
33
|
+
|
|
34
|
+
client = Arplyx(api_key="ak_live_...")
|
|
35
|
+
|
|
36
|
+
# Enviar un texto
|
|
37
|
+
msg = client.send_message(
|
|
38
|
+
external_id="pedido-10045",
|
|
39
|
+
to="+5491155551234",
|
|
40
|
+
text="Tu pedido fue confirmado.",
|
|
41
|
+
channel="whatsapp_direct",
|
|
42
|
+
whatsapp_account_id="TU_ACCOUNT_ID",
|
|
43
|
+
)
|
|
44
|
+
print(msg.message_id, msg.status)
|
|
45
|
+
|
|
46
|
+
# Consultar estado (por message_id o por tu external_id)
|
|
47
|
+
status = client.get_message("pedido-10045")
|
|
48
|
+
print(status.status) # pending | queued | sent | delivered | read | failed
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Plantillas (Meta Official)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
client.send_message(
|
|
55
|
+
external_id="pedido-10045",
|
|
56
|
+
to="+5491155551234",
|
|
57
|
+
channel="whatsapp_meta",
|
|
58
|
+
template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Broadcast a una lista (planes Basic/Pro)
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
client.send_broadcast(
|
|
66
|
+
external_id="promo-junio-2026",
|
|
67
|
+
list_id="TU_LIST_ID",
|
|
68
|
+
text="Este mes 2x1 en turnos.",
|
|
69
|
+
channel="whatsapp_direct",
|
|
70
|
+
whatsapp_account_id="TU_ACCOUNT_ID",
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Listar cuentas de WhatsApp
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
for acc in client.list_whatsapp_accounts():
|
|
78
|
+
print(acc.id, acc.type, acc.status) # acc.id se usa como whatsapp_account_id
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Manejo de errores
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
try:
|
|
85
|
+
client.send_message(external_id="x", to="+549...", text="hola")
|
|
86
|
+
except ArplyxError as e:
|
|
87
|
+
print(e.status) # 400, 401, 409, 422, 429, 500...
|
|
88
|
+
print(e.code) # 'validation_error', 'conflict', 'quota_exceeded'...
|
|
89
|
+
print(e.details) # detalles por campo si es validation_error
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Idempotencia
|
|
93
|
+
|
|
94
|
+
Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.
|
|
95
|
+
|
|
96
|
+
## Configuración
|
|
97
|
+
|
|
98
|
+
| Parámetro | Requerido | Default |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `api_key` | Sí | — |
|
|
101
|
+
| `base_url` | No | `https://api.arplyx.com` |
|
|
102
|
+
| `timeout` | No | `10.0` |
|
|
103
|
+
| `session` | No | `requests.Session()` nueva |
|
|
104
|
+
|
|
105
|
+
Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
|
arplyx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# arplyx (Python SDK)
|
|
2
|
+
|
|
3
|
+
SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install arplyx
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requiere Python 3.8+.
|
|
12
|
+
|
|
13
|
+
## Uso
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from arplyx import Arplyx, ArplyxError
|
|
17
|
+
|
|
18
|
+
client = Arplyx(api_key="ak_live_...")
|
|
19
|
+
|
|
20
|
+
# Enviar un texto
|
|
21
|
+
msg = client.send_message(
|
|
22
|
+
external_id="pedido-10045",
|
|
23
|
+
to="+5491155551234",
|
|
24
|
+
text="Tu pedido fue confirmado.",
|
|
25
|
+
channel="whatsapp_direct",
|
|
26
|
+
whatsapp_account_id="TU_ACCOUNT_ID",
|
|
27
|
+
)
|
|
28
|
+
print(msg.message_id, msg.status)
|
|
29
|
+
|
|
30
|
+
# Consultar estado (por message_id o por tu external_id)
|
|
31
|
+
status = client.get_message("pedido-10045")
|
|
32
|
+
print(status.status) # pending | queued | sent | delivered | read | failed
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Plantillas (Meta Official)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
client.send_message(
|
|
39
|
+
external_id="pedido-10045",
|
|
40
|
+
to="+5491155551234",
|
|
41
|
+
channel="whatsapp_meta",
|
|
42
|
+
template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Broadcast a una lista (planes Basic/Pro)
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
client.send_broadcast(
|
|
50
|
+
external_id="promo-junio-2026",
|
|
51
|
+
list_id="TU_LIST_ID",
|
|
52
|
+
text="Este mes 2x1 en turnos.",
|
|
53
|
+
channel="whatsapp_direct",
|
|
54
|
+
whatsapp_account_id="TU_ACCOUNT_ID",
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Listar cuentas de WhatsApp
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
for acc in client.list_whatsapp_accounts():
|
|
62
|
+
print(acc.id, acc.type, acc.status) # acc.id se usa como whatsapp_account_id
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Manejo de errores
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
try:
|
|
69
|
+
client.send_message(external_id="x", to="+549...", text="hola")
|
|
70
|
+
except ArplyxError as e:
|
|
71
|
+
print(e.status) # 400, 401, 409, 422, 429, 500...
|
|
72
|
+
print(e.code) # 'validation_error', 'conflict', 'quota_exceeded'...
|
|
73
|
+
print(e.details) # detalles por campo si es validation_error
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Idempotencia
|
|
77
|
+
|
|
78
|
+
Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.
|
|
79
|
+
|
|
80
|
+
## Configuración
|
|
81
|
+
|
|
82
|
+
| Parámetro | Requerido | Default |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `api_key` | Sí | — |
|
|
85
|
+
| `base_url` | No | `https://api.arplyx.com` |
|
|
86
|
+
| `timeout` | No | `10.0` |
|
|
87
|
+
| `session` | No | `requests.Session()` nueva |
|
|
88
|
+
|
|
89
|
+
Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "arplyx"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SDK oficial de Arplyx: enviá mensajes y broadcasts de WhatsApp desde Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Arplyx", email = "info@arplyx.com" }]
|
|
13
|
+
keywords = ["arplyx", "whatsapp", "whatsapp-api", "sdk", "mensajeria", "broadcasts"]
|
|
14
|
+
dependencies = ["requests>=2.25"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://docs.arplyx.com/api/"
|
|
23
|
+
Documentation = "https://docs.arplyx.com/api/"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
arplyx-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""SDK oficial de Arplyx para Python."""
|
|
2
|
+
|
|
3
|
+
from .client import Arplyx
|
|
4
|
+
from .errors import ArplyxError
|
|
5
|
+
from .types import (
|
|
6
|
+
BroadcastResponse,
|
|
7
|
+
MessageResponse,
|
|
8
|
+
MessageStatus,
|
|
9
|
+
WhatsAppAccount,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Arplyx",
|
|
15
|
+
"ArplyxError",
|
|
16
|
+
"MessageResponse",
|
|
17
|
+
"BroadcastResponse",
|
|
18
|
+
"MessageStatus",
|
|
19
|
+
"WhatsAppAccount",
|
|
20
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from .errors import ArplyxError
|
|
6
|
+
from .types import (
|
|
7
|
+
BroadcastResponse,
|
|
8
|
+
MessageResponse,
|
|
9
|
+
MessageStatus,
|
|
10
|
+
WhatsAppAccount,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
DEFAULT_BASE_URL = "https://api.arplyx.com"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Arplyx:
|
|
17
|
+
"""Cliente de la API de Arplyx.
|
|
18
|
+
|
|
19
|
+
Ejemplo::
|
|
20
|
+
|
|
21
|
+
from arplyx import Arplyx
|
|
22
|
+
|
|
23
|
+
client = Arplyx(api_key="ak_live_...")
|
|
24
|
+
msg = client.send_message(
|
|
25
|
+
external_id="pedido-10045",
|
|
26
|
+
to="+5491155551234",
|
|
27
|
+
text="Tu pedido fue confirmado",
|
|
28
|
+
channel="whatsapp_direct",
|
|
29
|
+
whatsapp_account_id="...",
|
|
30
|
+
)
|
|
31
|
+
print(msg.message_id, msg.status)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
api_key: str,
|
|
37
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
38
|
+
timeout: float = 10.0,
|
|
39
|
+
session: Optional[requests.Session] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
if not api_key:
|
|
42
|
+
raise ValueError("Arplyx: falta api_key.")
|
|
43
|
+
self.api_key = api_key
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self._session = session or requests.Session()
|
|
47
|
+
|
|
48
|
+
def _request(self, method: str, path: str, json: Optional[dict] = None) -> Any:
|
|
49
|
+
resp = self._session.request(
|
|
50
|
+
method,
|
|
51
|
+
f"{self.base_url}{path}",
|
|
52
|
+
json=json,
|
|
53
|
+
headers={"x-api-key": self.api_key, "Content-Type": "application/json"},
|
|
54
|
+
timeout=self.timeout,
|
|
55
|
+
)
|
|
56
|
+
data: Any = None
|
|
57
|
+
if resp.content:
|
|
58
|
+
try:
|
|
59
|
+
data = resp.json()
|
|
60
|
+
except ValueError:
|
|
61
|
+
data = resp.text
|
|
62
|
+
if not resp.ok:
|
|
63
|
+
body = data if isinstance(data, dict) else {"error": data}
|
|
64
|
+
raise ArplyxError(resp.status_code, body)
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
def send_message(
|
|
68
|
+
self,
|
|
69
|
+
external_id: str,
|
|
70
|
+
to: str,
|
|
71
|
+
*,
|
|
72
|
+
text: Optional[str] = None,
|
|
73
|
+
template: Optional[Dict[str, Any]] = None,
|
|
74
|
+
channel: Optional[str] = None,
|
|
75
|
+
whatsapp_account_id: Optional[str] = None,
|
|
76
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
77
|
+
ttl_seconds: Optional[int] = None,
|
|
78
|
+
) -> MessageResponse:
|
|
79
|
+
"""Envía un mensaje de WhatsApp (texto o plantilla).
|
|
80
|
+
|
|
81
|
+
Indicá exactamente uno de ``text`` o ``template``. ``template`` es un
|
|
82
|
+
dict ``{"name", "language", "body": [...], "header": [...]}``.
|
|
83
|
+
"""
|
|
84
|
+
if (text is None) == (template is None):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"send_message: indicá exactamente uno de `text` o `template`."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if text is not None:
|
|
90
|
+
content: Dict[str, Any] = {"type": "text", "text": text}
|
|
91
|
+
else:
|
|
92
|
+
tpl: Dict[str, Any] = {
|
|
93
|
+
"name": template["name"],
|
|
94
|
+
"language": template["language"],
|
|
95
|
+
}
|
|
96
|
+
components: Dict[str, Any] = {}
|
|
97
|
+
if template.get("body"):
|
|
98
|
+
components["body"] = template["body"]
|
|
99
|
+
if template.get("header"):
|
|
100
|
+
components["header"] = template["header"]
|
|
101
|
+
if components:
|
|
102
|
+
tpl["components"] = components
|
|
103
|
+
content = {"type": "template", "template": tpl}
|
|
104
|
+
|
|
105
|
+
payload: Dict[str, Any] = {
|
|
106
|
+
"externalId": external_id,
|
|
107
|
+
"to": {"type": "phone", "phoneE164": to},
|
|
108
|
+
"content": content,
|
|
109
|
+
}
|
|
110
|
+
if channel is not None:
|
|
111
|
+
payload["channel"] = channel
|
|
112
|
+
if whatsapp_account_id is not None:
|
|
113
|
+
payload["whatsappAccountId"] = whatsapp_account_id
|
|
114
|
+
if metadata is not None:
|
|
115
|
+
payload["metadata"] = metadata
|
|
116
|
+
if ttl_seconds is not None:
|
|
117
|
+
payload["ttlSeconds"] = ttl_seconds
|
|
118
|
+
|
|
119
|
+
return MessageResponse._from_api(self._request("POST", "/messages", payload))
|
|
120
|
+
|
|
121
|
+
def send_broadcast(
|
|
122
|
+
self,
|
|
123
|
+
external_id: str,
|
|
124
|
+
list_id: str,
|
|
125
|
+
text: str,
|
|
126
|
+
channel: str,
|
|
127
|
+
*,
|
|
128
|
+
whatsapp_account_id: Optional[str] = None,
|
|
129
|
+
ttl_seconds: Optional[int] = None,
|
|
130
|
+
) -> BroadcastResponse:
|
|
131
|
+
"""Envía un texto a todos los contactos de una lista (planes Basic/Pro)."""
|
|
132
|
+
payload: Dict[str, Any] = {
|
|
133
|
+
"externalId": external_id,
|
|
134
|
+
"listId": list_id,
|
|
135
|
+
"channel": channel,
|
|
136
|
+
"content": {"type": "text", "text": text},
|
|
137
|
+
}
|
|
138
|
+
if whatsapp_account_id is not None:
|
|
139
|
+
payload["whatsappAccountId"] = whatsapp_account_id
|
|
140
|
+
if ttl_seconds is not None:
|
|
141
|
+
payload["ttlSeconds"] = ttl_seconds
|
|
142
|
+
|
|
143
|
+
return BroadcastResponse._from_api(
|
|
144
|
+
self._request("POST", "/messages/broadcast", payload)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def list_whatsapp_accounts(self) -> List[WhatsAppAccount]:
|
|
148
|
+
"""Lista las cuentas de WhatsApp del cliente."""
|
|
149
|
+
data = self._request("GET", "/whatsapp-accounts")
|
|
150
|
+
return [WhatsAppAccount._from_api(a) for a in data["accounts"]]
|
|
151
|
+
|
|
152
|
+
def get_message(self, id_or_external_id: str) -> MessageStatus:
|
|
153
|
+
"""Estado de un mensaje por su ``message_id`` o tu ``external_id``."""
|
|
154
|
+
from urllib.parse import quote
|
|
155
|
+
|
|
156
|
+
data = self._request("GET", f"/messages/{quote(id_or_external_id, safe='')}")
|
|
157
|
+
return MessageStatus._from_api(data)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any, List, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ArplyxError(Exception):
|
|
5
|
+
"""Error devuelto por la API de Arplyx (respuesta HTTP no-2xx).
|
|
6
|
+
|
|
7
|
+
Atributos:
|
|
8
|
+
status: código HTTP.
|
|
9
|
+
code: código de error de Arplyx (campo ``error`` del cuerpo), p. ej.
|
|
10
|
+
``validation_error``, ``conflict``, ``quota_exceeded``,
|
|
11
|
+
``invalid_whatsapp_account``, ``templates_not_available``.
|
|
12
|
+
body: cuerpo crudo de la respuesta.
|
|
13
|
+
details: detalles por campo si ``code == "validation_error"``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, status: int, body: Any) -> None:
|
|
17
|
+
self.status = status
|
|
18
|
+
self.body = body
|
|
19
|
+
code: Optional[str] = None
|
|
20
|
+
message: Optional[str] = None
|
|
21
|
+
details: Optional[List[dict]] = None
|
|
22
|
+
if isinstance(body, dict):
|
|
23
|
+
code = body.get("error")
|
|
24
|
+
message = body.get("message")
|
|
25
|
+
if isinstance(body.get("details"), list):
|
|
26
|
+
details = body["details"]
|
|
27
|
+
self.code = code or f"http_{status}"
|
|
28
|
+
self.details = details
|
|
29
|
+
super().__init__(f"Arplyx {status} ({self.code}): {message or self.code}")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class MessageResponse:
|
|
7
|
+
message_id: str
|
|
8
|
+
external_id: str
|
|
9
|
+
status: str
|
|
10
|
+
queued: bool
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def _from_api(d: dict) -> "MessageResponse":
|
|
14
|
+
return MessageResponse(
|
|
15
|
+
message_id=d["messageId"],
|
|
16
|
+
external_id=d["externalId"],
|
|
17
|
+
status=d["status"],
|
|
18
|
+
queued=d["queued"],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class BroadcastResponse:
|
|
24
|
+
broadcast_id: str
|
|
25
|
+
external_id: str
|
|
26
|
+
list_id: str
|
|
27
|
+
message_count: int
|
|
28
|
+
status: str
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _from_api(d: dict) -> "BroadcastResponse":
|
|
32
|
+
return BroadcastResponse(
|
|
33
|
+
broadcast_id=d["broadcastId"],
|
|
34
|
+
external_id=d["externalId"],
|
|
35
|
+
list_id=d["listId"],
|
|
36
|
+
message_count=d["messageCount"],
|
|
37
|
+
status=d["status"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class MessageStatus:
|
|
43
|
+
message_id: str
|
|
44
|
+
external_id: str
|
|
45
|
+
recipient: str
|
|
46
|
+
channel: str
|
|
47
|
+
status: str
|
|
48
|
+
attempt_count: int
|
|
49
|
+
last_error_code: Optional[str]
|
|
50
|
+
broadcast_id: Optional[str]
|
|
51
|
+
created_at: str
|
|
52
|
+
sent_at: Optional[str]
|
|
53
|
+
delivered_at: Optional[str]
|
|
54
|
+
failed_at: Optional[str]
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _from_api(d: dict) -> "MessageStatus":
|
|
58
|
+
return MessageStatus(
|
|
59
|
+
message_id=d["messageId"],
|
|
60
|
+
external_id=d["externalId"],
|
|
61
|
+
recipient=d["recipient"],
|
|
62
|
+
channel=d["channel"],
|
|
63
|
+
status=d["status"],
|
|
64
|
+
attempt_count=d["attemptCount"],
|
|
65
|
+
last_error_code=d.get("lastErrorCode"),
|
|
66
|
+
broadcast_id=d.get("broadcastId"),
|
|
67
|
+
created_at=d["createdAt"],
|
|
68
|
+
sent_at=d.get("sentAt"),
|
|
69
|
+
delivered_at=d.get("deliveredAt"),
|
|
70
|
+
failed_at=d.get("failedAt"),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class WhatsAppAccount:
|
|
76
|
+
id: str
|
|
77
|
+
type: str
|
|
78
|
+
status: str
|
|
79
|
+
phone_number: Optional[str]
|
|
80
|
+
display_name: Optional[str]
|
|
81
|
+
connected_at: Optional[str]
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _from_api(d: dict) -> "WhatsAppAccount":
|
|
85
|
+
return WhatsAppAccount(
|
|
86
|
+
id=d["id"],
|
|
87
|
+
type=d["type"],
|
|
88
|
+
status=d["status"],
|
|
89
|
+
phone_number=d.get("phoneNumber"),
|
|
90
|
+
display_name=d.get("displayName"),
|
|
91
|
+
connected_at=d.get("connectedAt"),
|
|
92
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arplyx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK oficial de Arplyx: enviá mensajes y broadcasts de WhatsApp desde Python.
|
|
5
|
+
Author-email: Arplyx <info@arplyx.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://docs.arplyx.com/api/
|
|
8
|
+
Project-URL: Documentation, https://docs.arplyx.com/api/
|
|
9
|
+
Keywords: arplyx,whatsapp,whatsapp-api,sdk,mensajeria,broadcasts
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: requests>=2.25
|
|
16
|
+
|
|
17
|
+
# arplyx (Python SDK)
|
|
18
|
+
|
|
19
|
+
SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.
|
|
20
|
+
|
|
21
|
+
## Instalación
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install arplyx
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requiere Python 3.8+.
|
|
28
|
+
|
|
29
|
+
## Uso
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from arplyx import Arplyx, ArplyxError
|
|
33
|
+
|
|
34
|
+
client = Arplyx(api_key="ak_live_...")
|
|
35
|
+
|
|
36
|
+
# Enviar un texto
|
|
37
|
+
msg = client.send_message(
|
|
38
|
+
external_id="pedido-10045",
|
|
39
|
+
to="+5491155551234",
|
|
40
|
+
text="Tu pedido fue confirmado.",
|
|
41
|
+
channel="whatsapp_direct",
|
|
42
|
+
whatsapp_account_id="TU_ACCOUNT_ID",
|
|
43
|
+
)
|
|
44
|
+
print(msg.message_id, msg.status)
|
|
45
|
+
|
|
46
|
+
# Consultar estado (por message_id o por tu external_id)
|
|
47
|
+
status = client.get_message("pedido-10045")
|
|
48
|
+
print(status.status) # pending | queued | sent | delivered | read | failed
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Plantillas (Meta Official)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
client.send_message(
|
|
55
|
+
external_id="pedido-10045",
|
|
56
|
+
to="+5491155551234",
|
|
57
|
+
channel="whatsapp_meta",
|
|
58
|
+
template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Broadcast a una lista (planes Basic/Pro)
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
client.send_broadcast(
|
|
66
|
+
external_id="promo-junio-2026",
|
|
67
|
+
list_id="TU_LIST_ID",
|
|
68
|
+
text="Este mes 2x1 en turnos.",
|
|
69
|
+
channel="whatsapp_direct",
|
|
70
|
+
whatsapp_account_id="TU_ACCOUNT_ID",
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Listar cuentas de WhatsApp
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
for acc in client.list_whatsapp_accounts():
|
|
78
|
+
print(acc.id, acc.type, acc.status) # acc.id se usa como whatsapp_account_id
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Manejo de errores
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
try:
|
|
85
|
+
client.send_message(external_id="x", to="+549...", text="hola")
|
|
86
|
+
except ArplyxError as e:
|
|
87
|
+
print(e.status) # 400, 401, 409, 422, 429, 500...
|
|
88
|
+
print(e.code) # 'validation_error', 'conflict', 'quota_exceeded'...
|
|
89
|
+
print(e.details) # detalles por campo si es validation_error
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Idempotencia
|
|
93
|
+
|
|
94
|
+
Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.
|
|
95
|
+
|
|
96
|
+
## Configuración
|
|
97
|
+
|
|
98
|
+
| Parámetro | Requerido | Default |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `api_key` | Sí | — |
|
|
101
|
+
| `base_url` | No | `https://api.arplyx.com` |
|
|
102
|
+
| `timeout` | No | `10.0` |
|
|
103
|
+
| `session` | No | `requests.Session()` nueva |
|
|
104
|
+
|
|
105
|
+
Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/arplyx/__init__.py
|
|
4
|
+
src/arplyx/client.py
|
|
5
|
+
src/arplyx/errors.py
|
|
6
|
+
src/arplyx/types.py
|
|
7
|
+
src/arplyx.egg-info/PKG-INFO
|
|
8
|
+
src/arplyx.egg-info/SOURCES.txt
|
|
9
|
+
src/arplyx.egg-info/dependency_links.txt
|
|
10
|
+
src/arplyx.egg-info/requires.txt
|
|
11
|
+
src/arplyx.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
arplyx
|