vaksms-client 0.2.0__py3-none-any.whl
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.
- vak_sms/__init__.py +14 -0
- vak_sms/client.py +365 -0
- vak_sms/errors.py +30 -0
- vak_sms/types.py +31 -0
- vaksms_client-0.2.0.dist-info/METADATA +193 -0
- vaksms_client-0.2.0.dist-info/RECORD +8 -0
- vaksms_client-0.2.0.dist-info/WHEEL +4 -0
- vaksms_client-0.2.0.dist-info/licenses/LICENSE +15 -0
vak_sms/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""VAK-SMS Python Client."""
|
|
2
|
+
|
|
3
|
+
from .client import DEFAULT_SOFT_ID, VakSmsClient
|
|
4
|
+
from .errors import VakSmsError
|
|
5
|
+
from .types import CountNumber, CountryOperator, VakNumber
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"DEFAULT_SOFT_ID",
|
|
9
|
+
"CountNumber",
|
|
10
|
+
"CountryOperator",
|
|
11
|
+
"VakNumber",
|
|
12
|
+
"VakSmsClient",
|
|
13
|
+
"VakSmsError",
|
|
14
|
+
]
|
vak_sms/client.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"""VAK-SMS API client implementation."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .errors import ERROR_DESCRIPTIONS, VakSmsError
|
|
9
|
+
from .types import CountNumber, CountryOperator, VakNumber
|
|
10
|
+
|
|
11
|
+
DEFAULT_SOFT_ID = ""
|
|
12
|
+
DEFAULT_BASE_URL = "https://vak-sms.com"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VakSmsClient:
|
|
16
|
+
"""Client for the VAK-SMS temporary number and SMS OTP reception service.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
api_key: VAK-SMS API key.
|
|
20
|
+
base_url: Base URL of the API. Defaults to https://vak-sms.com.
|
|
21
|
+
timeout: HTTP request timeout in seconds. Defaults to 30.0.
|
|
22
|
+
soft_id: Optional software/partner identifier.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
>>> from vak_sms import VakSmsClient
|
|
26
|
+
>>> client = VakSmsClient(api_key="your_api_key")
|
|
27
|
+
>>> balance = client.get_balance()
|
|
28
|
+
>>> print(balance)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
api_key: str,
|
|
34
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
35
|
+
timeout: float = 30.0,
|
|
36
|
+
soft_id: str | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
if not api_key or not api_key.strip():
|
|
39
|
+
raise VakSmsError("api_key is required to initialize VakSmsClient", "apiKeyNotFound")
|
|
40
|
+
self.api_key = api_key.strip()
|
|
41
|
+
self.base_url = base_url.rstrip("/")
|
|
42
|
+
self.soft_id = soft_id if soft_id is not None else DEFAULT_SOFT_ID
|
|
43
|
+
self._client = httpx.Client(base_url=self.base_url, timeout=timeout)
|
|
44
|
+
|
|
45
|
+
def __enter__(self) -> "VakSmsClient":
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
49
|
+
self.close()
|
|
50
|
+
|
|
51
|
+
def close(self) -> None:
|
|
52
|
+
"""Closes the underlying HTTP client session."""
|
|
53
|
+
self._client.close()
|
|
54
|
+
|
|
55
|
+
def get_balance(self) -> float:
|
|
56
|
+
"""Retrieves the current account balance in RUB.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The account balance as a float.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
VakSmsError: If the server returns an error or unexpected response.
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
>>> client = VakSmsClient(api_key="key")
|
|
66
|
+
>>> balance = client.get_balance()
|
|
67
|
+
>>> print(f"Balance: {balance} RUB")
|
|
68
|
+
"""
|
|
69
|
+
data = self._request("/api/getBalance/")
|
|
70
|
+
if (
|
|
71
|
+
isinstance(data, dict)
|
|
72
|
+
and "balance" in data
|
|
73
|
+
and isinstance(data["balance"], (int, float))
|
|
74
|
+
):
|
|
75
|
+
return float(data["balance"])
|
|
76
|
+
raise VakSmsError("Invalid getBalance response", "INVALID_RESPONSE", data)
|
|
77
|
+
|
|
78
|
+
def get_number(
|
|
79
|
+
self,
|
|
80
|
+
service: str | list[str],
|
|
81
|
+
country: str | None = None,
|
|
82
|
+
operator: str | None = None,
|
|
83
|
+
rent: bool = False,
|
|
84
|
+
soft_id: str | None = None,
|
|
85
|
+
) -> VakNumber:
|
|
86
|
+
"""Orders a temporary virtual phone number.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
service: Service code (e.g. 'tg', 'wa') or list of codes.
|
|
90
|
+
country: 2-letter country code (e.g. 'ru', 'kz').
|
|
91
|
+
operator: Optional network operator name (e.g. 'megafon').
|
|
92
|
+
rent: Whether to lease number under extended rental.
|
|
93
|
+
soft_id: Optional software/partner identifier.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
VakNumber containing phone number and idNum.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
VakSmsError: If the number cannot be ordered or stock is exhausted.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
>>> client = VakSmsClient(api_key="key")
|
|
103
|
+
>>> number = client.get_number("tg", country="ru")
|
|
104
|
+
>>> print(number.tel, number.id_num)
|
|
105
|
+
"""
|
|
106
|
+
srv = ",".join(service) if isinstance(service, list) else service
|
|
107
|
+
params: dict[str, Any] = {"service": srv}
|
|
108
|
+
|
|
109
|
+
if country:
|
|
110
|
+
params["country"] = country
|
|
111
|
+
if operator:
|
|
112
|
+
params["operator"] = operator
|
|
113
|
+
if rent:
|
|
114
|
+
params["rent"] = "true"
|
|
115
|
+
|
|
116
|
+
effective_soft_id = soft_id if soft_id is not None else self.soft_id
|
|
117
|
+
if effective_soft_id:
|
|
118
|
+
params["softId"] = effective_soft_id
|
|
119
|
+
|
|
120
|
+
data = self._request("/api/getNumber/", params)
|
|
121
|
+
if isinstance(data, dict) and "idNum" in data and "tel" in data:
|
|
122
|
+
return VakNumber(
|
|
123
|
+
id_num=str(data["idNum"]),
|
|
124
|
+
tel=str(data["tel"]),
|
|
125
|
+
service=srv,
|
|
126
|
+
rent=rent,
|
|
127
|
+
)
|
|
128
|
+
raise VakSmsError("Invalid getNumber response", "INVALID_RESPONSE", data)
|
|
129
|
+
|
|
130
|
+
def prolong_number(
|
|
131
|
+
self,
|
|
132
|
+
service: str,
|
|
133
|
+
tel: str | int,
|
|
134
|
+
soft_id: str | None = None,
|
|
135
|
+
) -> VakNumber:
|
|
136
|
+
"""Extends rental duration for an existing phone number.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
service: Service code.
|
|
140
|
+
tel: Phone number without plus sign.
|
|
141
|
+
soft_id: Optional software/partner identifier.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
VakNumber with extended order details.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
VakSmsError: If extension request fails.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
>>> client = VakSmsClient(api_key="key")
|
|
151
|
+
>>> order = client.prolong_number("tg", "79991234567")
|
|
152
|
+
>>> print(order.id_num)
|
|
153
|
+
"""
|
|
154
|
+
params: dict[str, Any] = {"service": service, "tel": str(tel)}
|
|
155
|
+
effective_soft_id = soft_id if soft_id is not None else self.soft_id
|
|
156
|
+
if effective_soft_id:
|
|
157
|
+
params["softId"] = effective_soft_id
|
|
158
|
+
|
|
159
|
+
data = self._request("/api/prolongNumber/", params)
|
|
160
|
+
if isinstance(data, dict) and "idNum" in data and "tel" in data:
|
|
161
|
+
return VakNumber(
|
|
162
|
+
id_num=str(data["idNum"]),
|
|
163
|
+
tel=str(data["tel"]),
|
|
164
|
+
service=service,
|
|
165
|
+
)
|
|
166
|
+
raise VakSmsError("Invalid prolongNumber response", "INVALID_RESPONSE", data)
|
|
167
|
+
|
|
168
|
+
def get_sms_code(
|
|
169
|
+
self,
|
|
170
|
+
id_num: str,
|
|
171
|
+
all_codes: bool = False,
|
|
172
|
+
) -> str | list[str] | None:
|
|
173
|
+
"""Retrieves incoming SMS code(s) for an active order.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
id_num: Activation identifier.
|
|
177
|
+
all_codes: Whether to return all received SMS codes.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Single code string, list of strings, or None if code is not yet received.
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
>>> client = VakSmsClient(api_key="key")
|
|
184
|
+
>>> code = client.get_sms_code("abcd1234ef")
|
|
185
|
+
>>> print("Received SMS code:", code)
|
|
186
|
+
"""
|
|
187
|
+
params: dict[str, Any] = {"idNum": id_num}
|
|
188
|
+
if all_codes:
|
|
189
|
+
params["all"] = "true"
|
|
190
|
+
|
|
191
|
+
data = self._request("/api/getSmsCode/", params)
|
|
192
|
+
if isinstance(data, dict) and "smsCode" in data:
|
|
193
|
+
return data["smsCode"]
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
def wait_for_code(
|
|
197
|
+
self,
|
|
198
|
+
id_num: str,
|
|
199
|
+
timeout: float = 120.0,
|
|
200
|
+
interval: float = 3.0,
|
|
201
|
+
) -> str | None:
|
|
202
|
+
"""Polls until an SMS code arrives or timeout expires.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
id_num: Activation identifier.
|
|
206
|
+
timeout: Maximum seconds to wait. Defaults to 120.0.
|
|
207
|
+
interval: Polling frequency in seconds. Defaults to 3.0.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Received SMS code string or None if timed out.
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
>>> client = VakSmsClient(api_key="key")
|
|
214
|
+
>>> code = client.wait_for_code("abcd1234ef", timeout=60.0)
|
|
215
|
+
>>> print("SMS code:", code)
|
|
216
|
+
"""
|
|
217
|
+
start = time.time()
|
|
218
|
+
while time.time() - start < timeout:
|
|
219
|
+
code = self.get_sms_code(id_num)
|
|
220
|
+
if code:
|
|
221
|
+
if isinstance(code, list):
|
|
222
|
+
return code[-1] if code else None
|
|
223
|
+
return str(code)
|
|
224
|
+
time.sleep(interval)
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def set_status(self, id_num: str, status: str) -> str:
|
|
228
|
+
"""Updates the status of an ongoing number activation.
|
|
229
|
+
|
|
230
|
+
Status options:
|
|
231
|
+
- 'send': Wait for another SMS code
|
|
232
|
+
- 'end': Successfully finish activation
|
|
233
|
+
- 'bad': Report number as defective/cancel
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
id_num: Activation identifier.
|
|
237
|
+
status: Status command ('send', 'end', 'bad').
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Status confirmation string.
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
>>> client = VakSmsClient(api_key="key")
|
|
244
|
+
>>> client.set_status("abcd1234ef", "end")
|
|
245
|
+
'update'
|
|
246
|
+
"""
|
|
247
|
+
data = self._request("/api/setStatus/", {"idNum": id_num, "status": status})
|
|
248
|
+
if isinstance(data, dict) and "status" in data:
|
|
249
|
+
return str(data["status"])
|
|
250
|
+
return "ok"
|
|
251
|
+
|
|
252
|
+
def get_count_number(
|
|
253
|
+
self,
|
|
254
|
+
service: str,
|
|
255
|
+
country: str = "ru",
|
|
256
|
+
operator: str | None = None,
|
|
257
|
+
) -> CountNumber:
|
|
258
|
+
"""Queries available number count and unit price for a service.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
service: Service code.
|
|
262
|
+
country: Country code (default 'ru').
|
|
263
|
+
operator: Optional network operator name.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
CountNumber object with stock and price.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
>>> client = VakSmsClient(api_key="key")
|
|
270
|
+
>>> count = client.get_count_number("tg", "ru")
|
|
271
|
+
>>> print(count.count, count.price)
|
|
272
|
+
"""
|
|
273
|
+
params: dict[str, Any] = {"service": service, "country": country, "price": 1}
|
|
274
|
+
if operator:
|
|
275
|
+
params["operator"] = operator
|
|
276
|
+
|
|
277
|
+
data = self._request("/api/getCountNumber/", params)
|
|
278
|
+
if isinstance(data, dict):
|
|
279
|
+
cnt = data.get(service, 0)
|
|
280
|
+
price = data.get("price")
|
|
281
|
+
return CountNumber(
|
|
282
|
+
service=service,
|
|
283
|
+
count=int(cnt) if isinstance(cnt, (int, float)) else 0,
|
|
284
|
+
price=float(price) if isinstance(price, (int, float)) else None,
|
|
285
|
+
)
|
|
286
|
+
raise VakSmsError("Invalid getCountNumber response", "INVALID_RESPONSE", data)
|
|
287
|
+
|
|
288
|
+
def get_country_list(self) -> list[CountryOperator]:
|
|
289
|
+
"""Retrieves catalog of supported countries and available operators.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of CountryOperator objects.
|
|
293
|
+
|
|
294
|
+
Examples:
|
|
295
|
+
>>> client = VakSmsClient(api_key="key")
|
|
296
|
+
>>> countries = client.get_country_list()
|
|
297
|
+
>>> print(f"Supported countries: {len(countries)}")
|
|
298
|
+
"""
|
|
299
|
+
data = self._request("/api/getCountryList/")
|
|
300
|
+
if isinstance(data, list):
|
|
301
|
+
res: list[CountryOperator] = []
|
|
302
|
+
for item in data:
|
|
303
|
+
if isinstance(item, dict):
|
|
304
|
+
res.append(
|
|
305
|
+
CountryOperator(
|
|
306
|
+
country_name=item.get("countryName", ""),
|
|
307
|
+
country_code=item.get("countryCode", ""),
|
|
308
|
+
operator_list=item.get("operatorList", []),
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
return res
|
|
312
|
+
raise VakSmsError("Invalid getCountryList response", "INVALID_RESPONSE", data)
|
|
313
|
+
|
|
314
|
+
def get_count_numbers_list(
|
|
315
|
+
self,
|
|
316
|
+
country: str = "ru",
|
|
317
|
+
operator: str | None = None,
|
|
318
|
+
rent: bool = False,
|
|
319
|
+
) -> dict[str, Any]:
|
|
320
|
+
"""Retrieves full service inventory for a country.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
country: Country code (default 'ru').
|
|
324
|
+
operator: Optional network operator name.
|
|
325
|
+
rent: Whether to query extended rental stock.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dictionary of services, quantities, and pricing.
|
|
329
|
+
|
|
330
|
+
Examples:
|
|
331
|
+
>>> client = VakSmsClient(api_key="key")
|
|
332
|
+
>>> catalog = client.get_count_numbers_list("ru")
|
|
333
|
+
"""
|
|
334
|
+
params: dict[str, Any] = {"country": country}
|
|
335
|
+
if operator:
|
|
336
|
+
params["operator"] = operator
|
|
337
|
+
if rent:
|
|
338
|
+
params["rent"] = "true"
|
|
339
|
+
|
|
340
|
+
data = self._request("/api/getCountNumbersList/", params)
|
|
341
|
+
return data if isinstance(data, dict) else {}
|
|
342
|
+
|
|
343
|
+
def _request(self, endpoint: str, params: dict[str, Any] | None = None) -> Any:
|
|
344
|
+
query: dict[str, Any] = {"apiKey": self.api_key}
|
|
345
|
+
if params:
|
|
346
|
+
query.update(params)
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
resp = self._client.get(endpoint, params=query)
|
|
350
|
+
resp.raise_for_status()
|
|
351
|
+
data = resp.json()
|
|
352
|
+
|
|
353
|
+
if isinstance(data, dict):
|
|
354
|
+
if "error" in data and data["error"]:
|
|
355
|
+
err_code = str(data["error"])
|
|
356
|
+
desc = ERROR_DESCRIPTIONS.get(err_code, f"VAK-SMS API error: {err_code}")
|
|
357
|
+
raise VakSmsError(desc, code=err_code, response_body=data)
|
|
358
|
+
if "code" in data and data.get("code") and "message" in data:
|
|
359
|
+
raise VakSmsError(
|
|
360
|
+
str(data["message"]), code=str(data["code"]), response_body=data
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
return data
|
|
364
|
+
except httpx.HTTPError as exc:
|
|
365
|
+
raise VakSmsError(f"HTTP request failed: {exc}", "NETWORK_ERROR") from exc
|
vak_sms/errors.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Error definitions for the VAK-SMS client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
ERROR_DESCRIPTIONS: dict[str, str] = {
|
|
6
|
+
"apiKeyNotFound": "API key not found",
|
|
7
|
+
"noService": "Service not found or invalid",
|
|
8
|
+
"noNumber": "No numbers available, try again later",
|
|
9
|
+
"noMoney": "Insufficient funds, please top up balance",
|
|
10
|
+
"noCountry": "Country not found",
|
|
11
|
+
"noOperator": "No operator was found for requested country",
|
|
12
|
+
"badStatus": "Status code not correct",
|
|
13
|
+
"idNumNotFound": "Activation idNum not found",
|
|
14
|
+
"badService": "Incorrect website or service code",
|
|
15
|
+
"badData": "Invalid request data sent",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VakSmsError(Exception):
|
|
20
|
+
"""Exception raised when a VAK-SMS API request fails."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
message: str,
|
|
25
|
+
code: str | None = None,
|
|
26
|
+
response_body: Any | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
self.code = code
|
|
30
|
+
self.response_body = response_body
|
vak_sms/types.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Data types for the VAK-SMS API."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class VakNumber:
|
|
8
|
+
"""Represents an acquired temporary phone number."""
|
|
9
|
+
|
|
10
|
+
id_num: str
|
|
11
|
+
tel: str
|
|
12
|
+
service: str | None = None
|
|
13
|
+
rent: bool = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class CountNumber:
|
|
18
|
+
"""Stock count and pricing for a service."""
|
|
19
|
+
|
|
20
|
+
service: str
|
|
21
|
+
count: int
|
|
22
|
+
price: float | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class CountryOperator:
|
|
27
|
+
"""Supported country and list of available mobile operators."""
|
|
28
|
+
|
|
29
|
+
country_name: str
|
|
30
|
+
country_code: str
|
|
31
|
+
operator_list: list[str]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vaksms-client
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python client for the VAK-SMS virtual number and SMS activation API
|
|
5
|
+
Project-URL: Repository, https://github.com/TysikGG/api-kit
|
|
6
|
+
Project-URL: Documentation, https://vak-sms.com/en/api/
|
|
7
|
+
License-Expression: ISC
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# vaksms-client
|
|
14
|
+
|
|
15
|
+
[](https://pypi.org/project/vaksms-client/)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://pypi.org/project/vaksms-client/)
|
|
18
|
+
|
|
19
|
+
Modern, fully typed Python client library for the [VAK-SMS](https://vak-sms.com/api/) virtual phone numbers and SMS OTP receiving service.
|
|
20
|
+
|
|
21
|
+
Official API documentation: <https://vak-sms.com/api/vak/>
|
|
22
|
+
|
|
23
|
+
Install as `vaksms-client` (`pip install vaksms-client`); import as `vak_sms`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- 📱 **Virtual Phone Numbers** — Disposable phone numbers across dozens of countries for Telegram, WhatsApp, Discord, Google, and 100+ services.
|
|
30
|
+
- 🔁 **Number Prolongation** — Re-rent or prolong previously used numbers for secondary SMS verification.
|
|
31
|
+
- ⏳ **Built-in Auto Polling** — `wait_for_code()` automatically polls until the SMS verification code is received or timeout is reached.
|
|
32
|
+
- 🐍 **Modern Pythonic Design** — Type annotations, dataclass models (`VakNumber`, `CountryOperator`), context manager support (`with VakSmsClient(...) as client:`), and custom exceptions.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install vaksms-client
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires Python 3.10 or higher.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start (Safe Check)
|
|
47
|
+
|
|
48
|
+
The quick check only inspects your account balance and supported countries. It does not order numbers or consume funds:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import os
|
|
52
|
+
from vak_sms import VakSmsClient
|
|
53
|
+
|
|
54
|
+
with VakSmsClient(api_key=os.environ["VAK_SMS_API_KEY"]) as client:
|
|
55
|
+
balance = client.get_balance()
|
|
56
|
+
print(f"Current VAK-SMS balance: {balance:.2f} RUB")
|
|
57
|
+
|
|
58
|
+
countries = client.get_country_list()
|
|
59
|
+
print(f"Supported countries count: {len(countries)}")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Usage Guide
|
|
65
|
+
|
|
66
|
+
### 1. Renting a Number and Receiving an SMS Code
|
|
67
|
+
|
|
68
|
+
Full activation lifecycle: order a number, wait for the incoming SMS verification code, and finish the activation:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from vak_sms import VakSmsClient, VakSmsError
|
|
72
|
+
|
|
73
|
+
with VakSmsClient(api_key="your_api_key") as client:
|
|
74
|
+
# 1. Order a number for Telegram in United States ('us')
|
|
75
|
+
order = client.get_number(service="tg", country="us")
|
|
76
|
+
print(f"Rented Number: +{order.tel} (ID: {order.id_num})")
|
|
77
|
+
|
|
78
|
+
# 2. Poll for the incoming verification code using built-in helper
|
|
79
|
+
try:
|
|
80
|
+
code = client.wait_for_code(order.id_num, timeout_ms=120_000, interval_ms=3_000)
|
|
81
|
+
print(f"Received verification code: {code}")
|
|
82
|
+
|
|
83
|
+
# 3. Mark activation successfully finished ('end')
|
|
84
|
+
client.set_status(order.id_num, "end")
|
|
85
|
+
except VakSmsError as err:
|
|
86
|
+
if err.code == "TIMEOUT":
|
|
87
|
+
print("SMS did not arrive in time. Marking as bad number ('bad')...")
|
|
88
|
+
client.set_status(order.id_num, "bad")
|
|
89
|
+
else:
|
|
90
|
+
raise err
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Requesting Repeat / Second SMS ('send')
|
|
94
|
+
|
|
95
|
+
If you need a second SMS message on the same rented number:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Notify provider that you want to receive another SMS code
|
|
99
|
+
client.set_status(order.id_num, "send")
|
|
100
|
+
|
|
101
|
+
# Wait for the second code
|
|
102
|
+
second_code = client.wait_for_code(order.id_num)
|
|
103
|
+
print(f"Second code: {second_code}")
|
|
104
|
+
|
|
105
|
+
# Finish
|
|
106
|
+
client.set_status(order.id_num, "end")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Number Prolongation (Re-using Previous Number)
|
|
110
|
+
|
|
111
|
+
Prolong an existing number for a service if it is still available on the SIM farm:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
prolonged = client.prolong_number("tg", order.id_num)
|
|
115
|
+
print(f"Prolonged number: +{prolonged.tel}")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. Inspecting Stock Availability
|
|
119
|
+
|
|
120
|
+
Check available numbers for a service:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Count available numbers for Telegram in USA
|
|
124
|
+
count = client.get_count_number("tg", "us")
|
|
125
|
+
print(f"Available numbers: {count}")
|
|
126
|
+
|
|
127
|
+
# All service counts in a country
|
|
128
|
+
numbers_list = client.get_count_numbers_list("us")
|
|
129
|
+
print("USA counts by service:", numbers_list)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## API Reference
|
|
135
|
+
|
|
136
|
+
### `VakSmsClient` Class
|
|
137
|
+
|
|
138
|
+
#### Constructor
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
VakSmsClient(
|
|
142
|
+
api_key: str,
|
|
143
|
+
base_url: str = "https://vak-sms.com",
|
|
144
|
+
timeout: float = 30.0,
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Parameter | Type | Default | Description |
|
|
149
|
+
| :--- | :--- | :--- | :--- |
|
|
150
|
+
| `api_key` | `str` | **Required** | Your VAK-SMS API key. |
|
|
151
|
+
| `base_url` | `str` | `"https://vak-sms.com"` | Base URL of VAK-SMS API. |
|
|
152
|
+
| `timeout` | `float` | `30.0` | Request timeout in seconds. |
|
|
153
|
+
|
|
154
|
+
#### Methods
|
|
155
|
+
|
|
156
|
+
- **`get_balance() -> float`** — Returns account balance in RUB.
|
|
157
|
+
- **`get_number(service, country=None, operator=None) -> VakNumber`** — Orders a phone number (`tel`, `id_num`).
|
|
158
|
+
- **`prolong_number(service, id_num, operator=None) -> VakNumber`** — Re-activates a previous number.
|
|
159
|
+
- **`get_sms_code(id_num, all=False) -> str | list[str] | None`** — Checks for incoming SMS code (`None` if waiting).
|
|
160
|
+
- **`wait_for_code(id_num, timeout_ms=120000, interval_ms=3000) -> str`** — Automatically polls until code arrives.
|
|
161
|
+
- **`set_status(id_num: str, status: str) -> str`** — Sets activation state (`'send'`, `'end'`, `'bad'`).
|
|
162
|
+
- **`get_count_number(service, country=None, operator=None) -> int`** — Available numbers for a service.
|
|
163
|
+
- **`get_country_list() -> list[CountryOperator]`** — Lists supported countries and operators.
|
|
164
|
+
- **`get_count_numbers_list(country=None, operator=None) -> dict[str, int]`** — Map of service counts.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Error Handling
|
|
169
|
+
|
|
170
|
+
When the API returns an error response, `VakSmsError` is raised:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from vak_sms import VakSmsClient, VakSmsError
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
with VakSmsClient(api_key="invalid_key") as client:
|
|
177
|
+
client.get_balance()
|
|
178
|
+
except VakSmsError as exc:
|
|
179
|
+
print(f"VAK-SMS Error: {exc.message} (Code: {exc.code})")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Common error codes:
|
|
183
|
+
- `noNumber` — No numbers currently available for the service/country.
|
|
184
|
+
- `noMoney` — Insufficient balance on account.
|
|
185
|
+
- `apiKeyNotFound` — Invalid API key.
|
|
186
|
+
- `badStatus` — Invalid status passed.
|
|
187
|
+
- `TIMEOUT` — Polling timeout exceeded in `wait_for_code()`.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
ISC
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
vak_sms/__init__.py,sha256=MFjL6gx1e_QVchAgsAfIhDbktQDMN6Gva2jWVtmArzo,307
|
|
2
|
+
vak_sms/client.py,sha256=kvBj9JSdmADsLwUWUp12X5RuhCFBP6TqJHzgLBi-_1c,12524
|
|
3
|
+
vak_sms/errors.py,sha256=MT_Pbtx2yyrF825LMYvFb69DU89fBSlVO29r7MYrPIo,960
|
|
4
|
+
vak_sms/types.py,sha256=YPIpaxiFGXKXe11Fn4gBe8dorZZmPc66gFv3CYxN6M8,567
|
|
5
|
+
vaksms_client-0.2.0.dist-info/METADATA,sha256=mv-2VywqM78jpjWSKLinP6vGl8y28sYu61M1xrp4D9Y,6270
|
|
6
|
+
vaksms_client-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
7
|
+
vaksms_client-0.2.0.dist-info/licenses/LICENSE,sha256=8S0uYmn_aeljwFudfBoV91HcIo79yab9s0yhMVsOHJ0,751
|
|
8
|
+
vaksms_client-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 api-kit contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|