mrxsim 1.0.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.
mrxsim-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MRXSIM
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.
mrxsim-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: mrxsim
3
+ Version: 1.0.0
4
+ Summary: Official Python client for MRXSIM.COM — secure SMS number purchasing & OTP retrieval
5
+ Author-email: MRXSIM <whomrxami@pm.me>
6
+ Maintainer-email: MRXSIM <whomrxami@pm.me>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://mrxsim.com
9
+ Project-URL: Documentation, https://mrxsim.com/docs
10
+ Project-URL: Repository, https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation
11
+ Project-URL: Bug Tracker, https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation/issues
12
+ Keywords: mrxsim,sms,otp,virtual-number,telegram,whatsapp,automation,api-client
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: requests<3,>=2.31.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
30
+ Requires-Dist: build>=1.2.0; extra == "dev"
31
+ Requires-Dist: twine>=5.0.0; extra == "dev"
32
+ Provides-Extra: async
33
+ Requires-Dist: aiohttp<4,>=3.9.0; extra == "async"
34
+ Provides-Extra: cli
35
+ Requires-Dist: colorama<1,>=0.4.6; extra == "cli"
36
+ Dynamic: license-file
37
+
38
+ # mrxsim
39
+
40
+ Official Python client for **[MRXSIM.COM](https://mrxsim.com)** — purchase virtual numbers and retrieve SMS OTPs for every catalog service (Telegram, WhatsApp, Google, Instagram, PayPal, Snapchat, Other (SMS), and more).
41
+
42
+ > Brand: **MRXSIM** · Domain: **https://mrxsim.com** · Package: **`mrxsim`**
43
+
44
+ ---
45
+
46
+ ## Security first
47
+
48
+ - **No hardcoded API keys** in library source or examples.
49
+ - Prefer environment variable ``MRXSIM_API_KEY``.
50
+ - Or load a local ``config.json`` that is **gitignored**.
51
+ - Never commit live keys. Revoke immediately if exposed.
52
+
53
+ ---
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install mrxsim
59
+ ```
60
+
61
+ From this repository (editable):
62
+
63
+ ```bash
64
+ pip install -e .
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Quick start (environment variable)
70
+
71
+ ```bash
72
+ export MRXSIM_API_KEY="mrxs_your_key_here" # Linux / macOS
73
+ # setx MRXSIM_API_KEY "mrxs_your_key_here" # Windows (new shell)
74
+ ```
75
+
76
+ ```python
77
+ from mrxsim import Client
78
+
79
+ with Client(country="england", service="telegram") as client:
80
+ order = client.get_number()
81
+ print(order["phone_number"], order["id"])
82
+
83
+ sms = client.wait_for_sms(order["id"])
84
+ print(sms["sms_code"])
85
+ ```
86
+
87
+ One-shot purchase + OTP:
88
+
89
+ ```python
90
+ from mrxsim import Client
91
+
92
+ with Client(country="egypt", service="whatsapp") as client:
93
+ result = client.buy_and_wait()
94
+ print(result["phone_number"], result["sms_code"])
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Quick start (config file)
100
+
101
+ ```bash
102
+ cp config.example.json config.json
103
+ # edit config.json → set api_key, country, service
104
+ ```
105
+
106
+ ```python
107
+ from mrxsim import Client
108
+
109
+ client = Client.from_config("config.json")
110
+ order = client.get_number()
111
+ sms = client.wait_for_sms(order["id"])
112
+ client.close()
113
+ ```
114
+
115
+ ``MRXSIM_API_KEY`` overrides ``api_key`` in the file when set.
116
+
117
+ ---
118
+
119
+ ## Get your API key
120
+
121
+ 1. Open [https://mrxsim.com](https://mrxsim.com) and create an account.
122
+ 2. Top up with **Crypto** (USDT / supported networks).
123
+ 3. **Profile → Get API KEY** (shown once at create/regenerate).
124
+ 4. Export ``MRXSIM_API_KEY`` or paste into gitignored ``config.json``.
125
+
126
+ ---
127
+
128
+ ## API surface
129
+
130
+ | Method | Endpoint | Client method |
131
+ |--------|----------|---------------|
132
+ | `POST` | `/api/v1/get_number` | `Client.get_number()` |
133
+ | `GET` | `/api/v1/get_sms?order_id=…` | `Client.get_sms()` / `wait_for_sms()` |
134
+
135
+ Header on every call:
136
+
137
+ ```http
138
+ X-API-Key: YOUR_KEY
139
+ ```
140
+
141
+ Docs: [https://mrxsim.com/docs](https://mrxsim.com/docs)
142
+
143
+ ---
144
+
145
+ ## Examples
146
+
147
+ ```bash
148
+ export MRXSIM_API_KEY="…"
149
+ python examples/buy_number_example.py
150
+ python examples/get_otp_example.py <order_id>
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ python -m venv .venv
159
+ # Windows: .venv\Scripts\activate
160
+ source .venv/bin/activate
161
+ pip install -e ".[dev]"
162
+ pytest
163
+ python -m build
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Support
169
+
170
+ - Site: [https://mrxsim.com](https://mrxsim.com)
171
+ - Docs: [https://mrxsim.com/docs](https://mrxsim.com/docs)
172
+ - Issues: [GitHub](https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation/issues)
173
+
174
+ © MRXSIM · Secure SMS Infrastructure
mrxsim-1.0.0/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # mrxsim
2
+
3
+ Official Python client for **[MRXSIM.COM](https://mrxsim.com)** — purchase virtual numbers and retrieve SMS OTPs for every catalog service (Telegram, WhatsApp, Google, Instagram, PayPal, Snapchat, Other (SMS), and more).
4
+
5
+ > Brand: **MRXSIM** · Domain: **https://mrxsim.com** · Package: **`mrxsim`**
6
+
7
+ ---
8
+
9
+ ## Security first
10
+
11
+ - **No hardcoded API keys** in library source or examples.
12
+ - Prefer environment variable ``MRXSIM_API_KEY``.
13
+ - Or load a local ``config.json`` that is **gitignored**.
14
+ - Never commit live keys. Revoke immediately if exposed.
15
+
16
+ ---
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install mrxsim
22
+ ```
23
+
24
+ From this repository (editable):
25
+
26
+ ```bash
27
+ pip install -e .
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick start (environment variable)
33
+
34
+ ```bash
35
+ export MRXSIM_API_KEY="mrxs_your_key_here" # Linux / macOS
36
+ # setx MRXSIM_API_KEY "mrxs_your_key_here" # Windows (new shell)
37
+ ```
38
+
39
+ ```python
40
+ from mrxsim import Client
41
+
42
+ with Client(country="england", service="telegram") as client:
43
+ order = client.get_number()
44
+ print(order["phone_number"], order["id"])
45
+
46
+ sms = client.wait_for_sms(order["id"])
47
+ print(sms["sms_code"])
48
+ ```
49
+
50
+ One-shot purchase + OTP:
51
+
52
+ ```python
53
+ from mrxsim import Client
54
+
55
+ with Client(country="egypt", service="whatsapp") as client:
56
+ result = client.buy_and_wait()
57
+ print(result["phone_number"], result["sms_code"])
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Quick start (config file)
63
+
64
+ ```bash
65
+ cp config.example.json config.json
66
+ # edit config.json → set api_key, country, service
67
+ ```
68
+
69
+ ```python
70
+ from mrxsim import Client
71
+
72
+ client = Client.from_config("config.json")
73
+ order = client.get_number()
74
+ sms = client.wait_for_sms(order["id"])
75
+ client.close()
76
+ ```
77
+
78
+ ``MRXSIM_API_KEY`` overrides ``api_key`` in the file when set.
79
+
80
+ ---
81
+
82
+ ## Get your API key
83
+
84
+ 1. Open [https://mrxsim.com](https://mrxsim.com) and create an account.
85
+ 2. Top up with **Crypto** (USDT / supported networks).
86
+ 3. **Profile → Get API KEY** (shown once at create/regenerate).
87
+ 4. Export ``MRXSIM_API_KEY`` or paste into gitignored ``config.json``.
88
+
89
+ ---
90
+
91
+ ## API surface
92
+
93
+ | Method | Endpoint | Client method |
94
+ |--------|----------|---------------|
95
+ | `POST` | `/api/v1/get_number` | `Client.get_number()` |
96
+ | `GET` | `/api/v1/get_sms?order_id=…` | `Client.get_sms()` / `wait_for_sms()` |
97
+
98
+ Header on every call:
99
+
100
+ ```http
101
+ X-API-Key: YOUR_KEY
102
+ ```
103
+
104
+ Docs: [https://mrxsim.com/docs](https://mrxsim.com/docs)
105
+
106
+ ---
107
+
108
+ ## Examples
109
+
110
+ ```bash
111
+ export MRXSIM_API_KEY="…"
112
+ python examples/buy_number_example.py
113
+ python examples/get_otp_example.py <order_id>
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ python -m venv .venv
122
+ # Windows: .venv\Scripts\activate
123
+ source .venv/bin/activate
124
+ pip install -e ".[dev]"
125
+ pytest
126
+ python -m build
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Support
132
+
133
+ - Site: [https://mrxsim.com](https://mrxsim.com)
134
+ - Docs: [https://mrxsim.com/docs](https://mrxsim.com/docs)
135
+ - Issues: [GitHub](https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation/issues)
136
+
137
+ © MRXSIM · Secure SMS Infrastructure
@@ -0,0 +1,34 @@
1
+ """
2
+ MRXSIM — Official Python client for https://mrxsim.com
3
+
4
+ Secure SMS number purchasing and OTP retrieval for every MRXSIM catalog service.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from mrxsim.client import Client, MrxsimClient, load_config
10
+ from mrxsim.exceptions import (
11
+ MrxsimAPIError,
12
+ MrxsimAuthError,
13
+ MrxsimConfigError,
14
+ MrxsimError,
15
+ MrxsimOrderError,
16
+ MrxsimRateLimitError,
17
+ MrxsimTimeoutError,
18
+ )
19
+
20
+ __all__ = [
21
+ "Client",
22
+ "MrxsimClient",
23
+ "load_config",
24
+ "MrxsimError",
25
+ "MrxsimConfigError",
26
+ "MrxsimAuthError",
27
+ "MrxsimAPIError",
28
+ "MrxsimTimeoutError",
29
+ "MrxsimRateLimitError",
30
+ "MrxsimOrderError",
31
+ ]
32
+
33
+ __version__ = "1.0.0"
34
+ __author__ = "MRXSIM"
@@ -0,0 +1,452 @@
1
+ """
2
+ MRXSIM synchronous HTTP client.
3
+
4
+ Authentication is never hardcoded. Provide credentials via:
5
+
6
+ * Environment variable ``MRXSIM_API_KEY``
7
+ * Explicit ``api_key=`` constructor argument
8
+ * A local config file (e.g. ``config.json``) loaded with :meth:`Client.from_config`
9
+
10
+ © MRXSIM · https://mrxsim.com
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import time
18
+ from pathlib import Path
19
+ from typing import Any, Mapping
20
+ from urllib.parse import urljoin
21
+
22
+ import requests
23
+
24
+ from mrxsim.exceptions import (
25
+ MrxsimAPIError,
26
+ MrxsimAuthError,
27
+ MrxsimConfigError,
28
+ MrxsimError,
29
+ MrxsimOrderError,
30
+ MrxsimRateLimitError,
31
+ MrxsimTimeoutError,
32
+ )
33
+
34
+ DEFAULT_BASE_URL = "https://mrxsim.com"
35
+ DEFAULT_USER_AGENT = "mrxsim-python/1.0.0 (+https://mrxsim.com)"
36
+ ENV_API_KEY = "MRXSIM_API_KEY"
37
+ ENV_BASE_URL = "MRXSIM_BASE_URL"
38
+
39
+ _TERMINAL_FAIL = frozenset({"CANCELED", "CANCELLED", "EXPIRED", "TIMEOUT", "FAILED"})
40
+
41
+
42
+ def _require_nonempty(value: str | None, field: str) -> str:
43
+ text = (value or "").strip()
44
+ if not text:
45
+ raise MrxsimConfigError(f"Missing required configuration: {field}")
46
+ return text
47
+
48
+
49
+ def _normalize_base_url(url: str) -> str:
50
+ return url.strip().rstrip("/")
51
+
52
+
53
+ def load_config(path: str | Path) -> dict[str, Any]:
54
+ """
55
+ Load a JSON configuration file.
56
+
57
+ The file must be a JSON object. ``api_key`` may be omitted if
58
+ ``MRXSIM_API_KEY`` is set in the environment.
59
+
60
+ Parameters
61
+ ----------
62
+ path:
63
+ Filesystem path to the JSON config (e.g. ``config.json``).
64
+
65
+ Returns
66
+ -------
67
+ dict[str, Any]
68
+ Parsed configuration mapping.
69
+
70
+ Raises
71
+ ------
72
+ MrxsimConfigError
73
+ If the file is missing or not a valid JSON object.
74
+ """
75
+ cfg_path = Path(path).expanduser().resolve()
76
+ if not cfg_path.is_file():
77
+ raise MrxsimConfigError(
78
+ f"Config file not found: {cfg_path}. "
79
+ "Copy config.example.json → config.json and set your API key, "
80
+ "or export MRXSIM_API_KEY."
81
+ )
82
+ try:
83
+ with cfg_path.open("r", encoding="utf-8") as fh:
84
+ data = json.load(fh)
85
+ except json.JSONDecodeError as exc:
86
+ raise MrxsimConfigError(f"Invalid JSON in {cfg_path}: {exc}") from exc
87
+ if not isinstance(data, dict):
88
+ raise MrxsimConfigError(f"Config file must be a JSON object: {cfg_path}")
89
+ return data
90
+
91
+
92
+ class Client:
93
+ """
94
+ Production client for the MRXSIM.COM public SMS API.
95
+
96
+ Supports every catalog service (Telegram, WhatsApp, Google, Instagram,
97
+ PayPal, Snapchat, Other (SMS), and more).
98
+
99
+ Parameters
100
+ ----------
101
+ api_key:
102
+ MRXSIM API key. If omitted, read from ``MRXSIM_API_KEY``.
103
+ base_url:
104
+ API origin. Defaults to ``https://mrxsim.com`` or ``MRXSIM_BASE_URL``.
105
+ country:
106
+ Default catalog country code (e.g. ``england``, ``usa``, ``egypt``).
107
+ service:
108
+ Default service code (e.g. ``telegram``, ``whatsapp``, ``other``).
109
+ operator:
110
+ Default operator stack (``any`` or a specific stack id).
111
+ timeout:
112
+ Default HTTP request timeout in seconds.
113
+ poll_interval:
114
+ Default seconds between OTP polls.
115
+ poll_timeout:
116
+ Default maximum seconds to wait for an OTP.
117
+ session:
118
+ Optional pre-configured :class:`requests.Session`.
119
+
120
+ Raises
121
+ ------
122
+ MrxsimConfigError
123
+ If no API key can be resolved.
124
+ MrxsimAuthError
125
+ If the resolved key looks like an unreplaced placeholder.
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ api_key: str | None = None,
131
+ *,
132
+ base_url: str | None = None,
133
+ country: str | None = None,
134
+ service: str | None = None,
135
+ operator: str = "any",
136
+ timeout: float = 30.0,
137
+ poll_interval: float = 3.0,
138
+ poll_timeout: float = 600.0,
139
+ session: requests.Session | None = None,
140
+ ) -> None:
141
+ resolved_key = (api_key or os.environ.get(ENV_API_KEY) or "").strip()
142
+ if not resolved_key:
143
+ raise MrxsimConfigError(
144
+ "API key required. Pass api_key=, set MRXSIM_API_KEY, "
145
+ "or use Client.from_config('config.json'). "
146
+ "Generate a key at https://mrxsim.com"
147
+ )
148
+ if resolved_key.startswith("REPLACE_") or resolved_key in {
149
+ "mrxs_your_key_here",
150
+ "YOUR_KEY",
151
+ "changeme",
152
+ }:
153
+ raise MrxsimAuthError(
154
+ "Placeholder API key detected. Set a real key from https://mrxsim.com"
155
+ )
156
+
157
+ env_base = os.environ.get(ENV_BASE_URL)
158
+ self.base_url = _normalize_base_url(
159
+ base_url or env_base or DEFAULT_BASE_URL
160
+ )
161
+ self.country = (country or "").strip().lower() or None
162
+ self.service = (service or "").strip().lower() or None
163
+ self.operator = ((operator or "any").strip().lower() or "any")
164
+ self.timeout = max(5.0, float(timeout))
165
+ self.poll_interval = max(1.0, float(poll_interval))
166
+ self.poll_timeout = max(30.0, float(poll_timeout))
167
+
168
+ self._session = session or requests.Session()
169
+ self._session.headers.update(
170
+ {
171
+ "X-API-Key": resolved_key,
172
+ "Content-Type": "application/json",
173
+ "Accept": "application/json",
174
+ "User-Agent": DEFAULT_USER_AGENT,
175
+ }
176
+ )
177
+ # Never keep a public attribute that echoes the key.
178
+ self._api_key_fingerprint = f"{resolved_key[:6]}…{resolved_key[-4:]}" if len(resolved_key) > 12 else "***"
179
+
180
+ @classmethod
181
+ def from_config(
182
+ cls,
183
+ path: str | Path = "config.json",
184
+ *,
185
+ session: requests.Session | None = None,
186
+ ) -> Client:
187
+ """
188
+ Construct a client from a JSON config file.
189
+
190
+ Environment variable ``MRXSIM_API_KEY`` overrides ``api_key`` in the file
191
+ when set (prefer secrets outside the working tree).
192
+
193
+ Parameters
194
+ ----------
195
+ path:
196
+ Path to the JSON config file.
197
+ session:
198
+ Optional shared :class:`requests.Session`.
199
+ """
200
+ raw = load_config(path)
201
+ api_key = os.environ.get(ENV_API_KEY) or str(raw.get("api_key") or "")
202
+ return cls(
203
+ api_key=api_key or None,
204
+ base_url=str(raw.get("base_url") or DEFAULT_BASE_URL),
205
+ country=str(raw.get("country") or "") or None,
206
+ service=str(raw.get("service") or "") or None,
207
+ operator=str(raw.get("operator") or "any"),
208
+ timeout=float(raw.get("request_timeout_seconds") or 30),
209
+ poll_interval=float(raw.get("poll_interval_seconds") or 3),
210
+ poll_timeout=float(raw.get("poll_timeout_seconds") or 600),
211
+ session=session,
212
+ )
213
+
214
+ def close(self) -> None:
215
+ """Close the underlying HTTP session."""
216
+ self._session.close()
217
+
218
+ def __enter__(self) -> Client:
219
+ return self
220
+
221
+ def __exit__(self, *exc: object) -> None:
222
+ self.close()
223
+
224
+ def _url(self, path: str) -> str:
225
+ return urljoin(f"{self.base_url}/", path.lstrip("/"))
226
+
227
+ def _request(
228
+ self,
229
+ method: str,
230
+ path: str,
231
+ *,
232
+ json_body: Mapping[str, Any] | None = None,
233
+ params: Mapping[str, Any] | None = None,
234
+ timeout: float | None = None,
235
+ ) -> dict[str, Any]:
236
+ try:
237
+ response = self._session.request(
238
+ method=method.upper(),
239
+ url=self._url(path),
240
+ json=dict(json_body) if json_body is not None else None,
241
+ params=dict(params) if params is not None else None,
242
+ timeout=timeout if timeout is not None else self.timeout,
243
+ )
244
+ except requests.Timeout as exc:
245
+ raise MrxsimTimeoutError(f"HTTP request timed out: {method} {path}") from exc
246
+ except requests.RequestException as exc:
247
+ raise MrxsimError(f"HTTP transport error: {exc}") from exc
248
+ return self._parse(response)
249
+
250
+ @staticmethod
251
+ def _parse(response: requests.Response) -> dict[str, Any]:
252
+ try:
253
+ body: Any = response.json()
254
+ except ValueError:
255
+ body = {"error": (response.text or "")[:300]}
256
+
257
+ if response.status_code in {401, 403}:
258
+ detail = body.get("detail") if isinstance(body, dict) else body
259
+ raise MrxsimAuthError(
260
+ f"Authentication failed (HTTP {response.status_code}): {detail}",
261
+ details=detail,
262
+ )
263
+ if response.status_code == 429:
264
+ detail = body.get("detail") if isinstance(body, dict) else body
265
+ raise MrxsimRateLimitError(
266
+ f"Rate limited (HTTP 429): {detail}",
267
+ status_code=429,
268
+ details=detail,
269
+ )
270
+ if response.status_code >= 400:
271
+ detail: Any
272
+ if isinstance(body, dict):
273
+ raw_detail = body.get("detail", body)
274
+ if isinstance(raw_detail, dict):
275
+ detail = raw_detail.get("error") or raw_detail
276
+ else:
277
+ detail = raw_detail
278
+ else:
279
+ detail = body
280
+ raise MrxsimAPIError(
281
+ f"HTTP {response.status_code}: {detail}",
282
+ status_code=response.status_code,
283
+ details=detail,
284
+ )
285
+ if not isinstance(body, dict):
286
+ raise MrxsimAPIError(
287
+ "Unexpected API response (expected JSON object)",
288
+ status_code=response.status_code,
289
+ details=body,
290
+ )
291
+ return body
292
+
293
+ def get_number(
294
+ self,
295
+ *,
296
+ country: str | None = None,
297
+ service: str | None = None,
298
+ operator: str | None = None,
299
+ ) -> dict[str, Any]:
300
+ """
301
+ Purchase a virtual number for a country/service pair.
302
+
303
+ Parameters
304
+ ----------
305
+ country:
306
+ Catalog country. Falls back to the client default.
307
+ service:
308
+ Catalog service. Falls back to the client default.
309
+ operator:
310
+ Operator stack. Falls back to the client default (``any``).
311
+
312
+ Returns
313
+ -------
314
+ dict[str, Any]
315
+ Order payload including ``id``, ``phone_number``, ``price``, ``status``.
316
+ """
317
+ resolved_country = _require_nonempty(country or self.country, "country")
318
+ resolved_service = _require_nonempty(service or self.service, "service")
319
+ resolved_operator = (
320
+ (operator if operator is not None else self.operator) or "any"
321
+ ).strip().lower() or "any"
322
+
323
+ return self._request(
324
+ "POST",
325
+ "/api/v1/get_number",
326
+ json_body={
327
+ "country": resolved_country.lower(),
328
+ "service": resolved_service.lower(),
329
+ "operator": resolved_operator,
330
+ },
331
+ )
332
+
333
+ def get_sms(self, order_id: str) -> dict[str, Any]:
334
+ """
335
+ Fetch the current SMS/OTP status for an order.
336
+
337
+ Parameters
338
+ ----------
339
+ order_id:
340
+ Order identifier returned by :meth:`get_number`.
341
+
342
+ Returns
343
+ -------
344
+ dict[str, Any]
345
+ Status payload; may include ``sms_code`` when received.
346
+ """
347
+ oid = _require_nonempty(order_id, "order_id")
348
+ return self._request(
349
+ "GET",
350
+ "/api/v1/get_sms",
351
+ params={"order_id": oid},
352
+ )
353
+
354
+ def wait_for_sms(
355
+ self,
356
+ order_id: str,
357
+ *,
358
+ poll_interval: float | None = None,
359
+ poll_timeout: float | None = None,
360
+ ) -> dict[str, Any]:
361
+ """
362
+ Poll until an OTP is received or the order fails / times out.
363
+
364
+ Parameters
365
+ ----------
366
+ order_id:
367
+ Order identifier from :meth:`get_number`.
368
+ poll_interval:
369
+ Seconds between polls (default: client ``poll_interval``).
370
+ poll_timeout:
371
+ Maximum wait in seconds (default: client ``poll_timeout``).
372
+
373
+ Returns
374
+ -------
375
+ dict[str, Any]
376
+ Final SMS payload containing ``sms_code`` when successful.
377
+
378
+ Raises
379
+ ------
380
+ MrxsimOrderError
381
+ If the order reaches a terminal failure status.
382
+ MrxsimTimeoutError
383
+ If no OTP arrives before ``poll_timeout``.
384
+ """
385
+ oid = _require_nonempty(order_id, "order_id")
386
+ interval = max(1.0, float(poll_interval if poll_interval is not None else self.poll_interval))
387
+ deadline = time.monotonic() + max(
388
+ 30.0, float(poll_timeout if poll_timeout is not None else self.poll_timeout)
389
+ )
390
+
391
+ while time.monotonic() < deadline:
392
+ time.sleep(interval)
393
+ row = self.get_sms(oid)
394
+ status = str(row.get("status") or "").upper()
395
+ code = str(row.get("sms_code") or "").strip()
396
+ if code or status == "RECEIVED":
397
+ return row
398
+ if status in _TERMINAL_FAIL:
399
+ raise MrxsimOrderError(
400
+ f"Order {oid} ended with status={status}",
401
+ status=status,
402
+ )
403
+
404
+ raise MrxsimTimeoutError(
405
+ f"Timed out waiting for SMS on order {oid}"
406
+ )
407
+
408
+ def buy_and_wait(
409
+ self,
410
+ *,
411
+ country: str | None = None,
412
+ service: str | None = None,
413
+ operator: str | None = None,
414
+ poll_interval: float | None = None,
415
+ poll_timeout: float | None = None,
416
+ ) -> dict[str, Any]:
417
+ """
418
+ Purchase a number and block until the OTP arrives.
419
+
420
+ Returns
421
+ -------
422
+ dict[str, Any]
423
+ Combined result with ``order``, ``phone_number``, ``sms_code``, and ``status``.
424
+ """
425
+ order = self.get_number(country=country, service=service, operator=operator)
426
+ order_id = str(order.get("id") or "")
427
+ if not order_id:
428
+ raise MrxsimAPIError("Purchase succeeded but no order id was returned", details=order)
429
+ sms = self.wait_for_sms(
430
+ order_id,
431
+ poll_interval=poll_interval,
432
+ poll_timeout=poll_timeout,
433
+ )
434
+ return {
435
+ "order": order,
436
+ "order_id": order_id,
437
+ "phone_number": sms.get("phone_number") or order.get("phone_number"),
438
+ "sms_code": str(sms.get("sms_code") or "").strip(),
439
+ "status": sms.get("status") or order.get("status"),
440
+ "sms": sms,
441
+ }
442
+
443
+ def __repr__(self) -> str:
444
+ return (
445
+ f"Client(base_url={self.base_url!r}, "
446
+ f"country={self.country!r}, service={self.service!r}, "
447
+ f"key={self._api_key_fingerprint!r})"
448
+ )
449
+
450
+
451
+ # Backwards-compatible alias used by early automation scripts.
452
+ MrxsimClient = Client
@@ -0,0 +1,55 @@
1
+ """Custom exception hierarchy for the MRXSIM public API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class MrxsimError(Exception):
9
+ """Base exception for all MRXSIM client errors."""
10
+
11
+ def __init__(self, message: str, *, details: Any | None = None) -> None:
12
+ super().__init__(message)
13
+ self.message = message
14
+ self.details = details
15
+
16
+ def __str__(self) -> str:
17
+ return self.message
18
+
19
+
20
+ class MrxsimConfigError(MrxsimError):
21
+ """Raised when configuration is missing, invalid, or insecure."""
22
+
23
+
24
+ class MrxsimAuthError(MrxsimError):
25
+ """Raised when the API key is missing, invalid, or unauthorized (HTTP 401/403)."""
26
+
27
+
28
+ class MrxsimAPIError(MrxsimError):
29
+ """Raised for non-success HTTP responses from the MRXSIM API."""
30
+
31
+ def __init__(
32
+ self,
33
+ message: str,
34
+ *,
35
+ status_code: int | None = None,
36
+ details: Any | None = None,
37
+ ) -> None:
38
+ super().__init__(message, details=details)
39
+ self.status_code = status_code
40
+
41
+
42
+ class MrxsimTimeoutError(MrxsimError):
43
+ """Raised when an HTTP request or OTP poll exceeds the configured timeout."""
44
+
45
+
46
+ class MrxsimRateLimitError(MrxsimAPIError):
47
+ """Raised when the API returns HTTP 429 (rate limited)."""
48
+
49
+
50
+ class MrxsimOrderError(MrxsimError):
51
+ """Raised when an order ends in a terminal failure state."""
52
+
53
+ def __init__(self, message: str, *, status: str | None = None) -> None:
54
+ super().__init__(message, details={"status": status} if status else None)
55
+ self.status = status
@@ -0,0 +1 @@
1
+ # Marker for PEP 561 typed package
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: mrxsim
3
+ Version: 1.0.0
4
+ Summary: Official Python client for MRXSIM.COM — secure SMS number purchasing & OTP retrieval
5
+ Author-email: MRXSIM <whomrxami@pm.me>
6
+ Maintainer-email: MRXSIM <whomrxami@pm.me>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://mrxsim.com
9
+ Project-URL: Documentation, https://mrxsim.com/docs
10
+ Project-URL: Repository, https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation
11
+ Project-URL: Bug Tracker, https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation/issues
12
+ Keywords: mrxsim,sms,otp,virtual-number,telegram,whatsapp,automation,api-client
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: requests<3,>=2.31.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
30
+ Requires-Dist: build>=1.2.0; extra == "dev"
31
+ Requires-Dist: twine>=5.0.0; extra == "dev"
32
+ Provides-Extra: async
33
+ Requires-Dist: aiohttp<4,>=3.9.0; extra == "async"
34
+ Provides-Extra: cli
35
+ Requires-Dist: colorama<1,>=0.4.6; extra == "cli"
36
+ Dynamic: license-file
37
+
38
+ # mrxsim
39
+
40
+ Official Python client for **[MRXSIM.COM](https://mrxsim.com)** — purchase virtual numbers and retrieve SMS OTPs for every catalog service (Telegram, WhatsApp, Google, Instagram, PayPal, Snapchat, Other (SMS), and more).
41
+
42
+ > Brand: **MRXSIM** · Domain: **https://mrxsim.com** · Package: **`mrxsim`**
43
+
44
+ ---
45
+
46
+ ## Security first
47
+
48
+ - **No hardcoded API keys** in library source or examples.
49
+ - Prefer environment variable ``MRXSIM_API_KEY``.
50
+ - Or load a local ``config.json`` that is **gitignored**.
51
+ - Never commit live keys. Revoke immediately if exposed.
52
+
53
+ ---
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install mrxsim
59
+ ```
60
+
61
+ From this repository (editable):
62
+
63
+ ```bash
64
+ pip install -e .
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Quick start (environment variable)
70
+
71
+ ```bash
72
+ export MRXSIM_API_KEY="mrxs_your_key_here" # Linux / macOS
73
+ # setx MRXSIM_API_KEY "mrxs_your_key_here" # Windows (new shell)
74
+ ```
75
+
76
+ ```python
77
+ from mrxsim import Client
78
+
79
+ with Client(country="england", service="telegram") as client:
80
+ order = client.get_number()
81
+ print(order["phone_number"], order["id"])
82
+
83
+ sms = client.wait_for_sms(order["id"])
84
+ print(sms["sms_code"])
85
+ ```
86
+
87
+ One-shot purchase + OTP:
88
+
89
+ ```python
90
+ from mrxsim import Client
91
+
92
+ with Client(country="egypt", service="whatsapp") as client:
93
+ result = client.buy_and_wait()
94
+ print(result["phone_number"], result["sms_code"])
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Quick start (config file)
100
+
101
+ ```bash
102
+ cp config.example.json config.json
103
+ # edit config.json → set api_key, country, service
104
+ ```
105
+
106
+ ```python
107
+ from mrxsim import Client
108
+
109
+ client = Client.from_config("config.json")
110
+ order = client.get_number()
111
+ sms = client.wait_for_sms(order["id"])
112
+ client.close()
113
+ ```
114
+
115
+ ``MRXSIM_API_KEY`` overrides ``api_key`` in the file when set.
116
+
117
+ ---
118
+
119
+ ## Get your API key
120
+
121
+ 1. Open [https://mrxsim.com](https://mrxsim.com) and create an account.
122
+ 2. Top up with **Crypto** (USDT / supported networks).
123
+ 3. **Profile → Get API KEY** (shown once at create/regenerate).
124
+ 4. Export ``MRXSIM_API_KEY`` or paste into gitignored ``config.json``.
125
+
126
+ ---
127
+
128
+ ## API surface
129
+
130
+ | Method | Endpoint | Client method |
131
+ |--------|----------|---------------|
132
+ | `POST` | `/api/v1/get_number` | `Client.get_number()` |
133
+ | `GET` | `/api/v1/get_sms?order_id=…` | `Client.get_sms()` / `wait_for_sms()` |
134
+
135
+ Header on every call:
136
+
137
+ ```http
138
+ X-API-Key: YOUR_KEY
139
+ ```
140
+
141
+ Docs: [https://mrxsim.com/docs](https://mrxsim.com/docs)
142
+
143
+ ---
144
+
145
+ ## Examples
146
+
147
+ ```bash
148
+ export MRXSIM_API_KEY="…"
149
+ python examples/buy_number_example.py
150
+ python examples/get_otp_example.py <order_id>
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ python -m venv .venv
159
+ # Windows: .venv\Scripts\activate
160
+ source .venv/bin/activate
161
+ pip install -e ".[dev]"
162
+ pytest
163
+ python -m build
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Support
169
+
170
+ - Site: [https://mrxsim.com](https://mrxsim.com)
171
+ - Docs: [https://mrxsim.com/docs](https://mrxsim.com/docs)
172
+ - Issues: [GitHub](https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation/issues)
173
+
174
+ © MRXSIM · Secure SMS Infrastructure
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ mrxsim/__init__.py
5
+ mrxsim/client.py
6
+ mrxsim/exceptions.py
7
+ mrxsim/py.typed
8
+ mrxsim.egg-info/PKG-INFO
9
+ mrxsim.egg-info/SOURCES.txt
10
+ mrxsim.egg-info/dependency_links.txt
11
+ mrxsim.egg-info/requires.txt
12
+ mrxsim.egg-info/top_level.txt
13
+ tests/test_client.py
@@ -0,0 +1,12 @@
1
+ requests<3,>=2.31.0
2
+
3
+ [async]
4
+ aiohttp<4,>=3.9.0
5
+
6
+ [cli]
7
+ colorama<1,>=0.4.6
8
+
9
+ [dev]
10
+ pytest>=8.0.0
11
+ build>=1.2.0
12
+ twine>=5.0.0
@@ -0,0 +1 @@
1
+ mrxsim
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mrxsim"
7
+ version = "1.0.0"
8
+ description = "Official Python client for MRXSIM.COM — secure SMS number purchasing & OTP retrieval"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "MRXSIM", email = "whomrxami@pm.me" },
15
+ ]
16
+ maintainers = [
17
+ { name = "MRXSIM", email = "whomrxami@pm.me" },
18
+ ]
19
+ keywords = [
20
+ "mrxsim",
21
+ "sms",
22
+ "otp",
23
+ "virtual-number",
24
+ "telegram",
25
+ "whatsapp",
26
+ "automation",
27
+ "api-client",
28
+ ]
29
+ classifiers = [
30
+ "Development Status :: 5 - Production/Stable",
31
+ "Intended Audience :: Developers",
32
+ "Operating System :: OS Independent",
33
+ "Programming Language :: Python :: 3",
34
+ "Programming Language :: Python :: 3.10",
35
+ "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ "Programming Language :: Python :: 3.13",
38
+ "Topic :: Internet :: WWW/HTTP",
39
+ "Topic :: Software Development :: Libraries :: Python Modules",
40
+ "Typing :: Typed",
41
+ ]
42
+ dependencies = [
43
+ "requests>=2.31.0,<3",
44
+ ]
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "pytest>=8.0.0",
49
+ "build>=1.2.0",
50
+ "twine>=5.0.0",
51
+ ]
52
+ async = [
53
+ "aiohttp>=3.9.0,<4",
54
+ ]
55
+ cli = [
56
+ "colorama>=0.4.6,<1",
57
+ ]
58
+
59
+ [project.urls]
60
+ Homepage = "https://mrxsim.com"
61
+ Documentation = "https://mrxsim.com/docs"
62
+ Repository = "https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation"
63
+ "Bug Tracker" = "https://github.com/MRXSIM/MRXSIM-Universal-SMS-Automation/issues"
64
+
65
+ [tool.setuptools.packages.find]
66
+ include = ["mrxsim*"]
67
+ exclude = ["tests*", "examples*"]
68
+
69
+ [tool.setuptools.package-data]
70
+ mrxsim = ["py.typed"]
71
+
72
+ [tool.pytest.ini_options]
73
+ testpaths = ["tests"]
74
+ pythonpath = ["."]
mrxsim-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,127 @@
1
+ """Basic unit tests for mrxsim (no live network / no real API keys)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pytest
10
+
11
+ from mrxsim import Client, MrxsimAuthError, MrxsimConfigError, __version__
12
+ from mrxsim.exceptions import MrxsimAPIError, MrxsimRateLimitError
13
+
14
+
15
+ def test_version() -> None:
16
+ assert __version__ == "1.0.0"
17
+
18
+
19
+ def test_client_requires_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
20
+ monkeypatch.delenv("MRXSIM_API_KEY", raising=False)
21
+ with pytest.raises(MrxsimConfigError):
22
+ Client()
23
+
24
+
25
+ def test_rejects_placeholder_key(monkeypatch: pytest.MonkeyPatch) -> None:
26
+ monkeypatch.delenv("MRXSIM_API_KEY", raising=False)
27
+ with pytest.raises(MrxsimAuthError):
28
+ Client(api_key="REPLACE_WITH_YOUR_MRXSIM_API_KEY")
29
+
30
+
31
+ def test_from_config_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
32
+ monkeypatch.delenv("MRXSIM_API_KEY", raising=False)
33
+ cfg = tmp_path / "config.json"
34
+ cfg.write_text(
35
+ json.dumps(
36
+ {
37
+ "api_key": "mrxs_test_key_abcdef123456",
38
+ "base_url": "https://mrxsim.com",
39
+ "country": "england",
40
+ "service": "telegram",
41
+ "operator": "any",
42
+ "poll_interval_seconds": 3,
43
+ "poll_timeout_seconds": 600,
44
+ "request_timeout_seconds": 30,
45
+ }
46
+ ),
47
+ encoding="utf-8",
48
+ )
49
+ client = Client.from_config(cfg)
50
+ assert client.country == "england"
51
+ assert client.service == "telegram"
52
+ assert client.base_url == "https://mrxsim.com"
53
+ client.close()
54
+
55
+
56
+ def test_env_overrides_config_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
57
+ cfg = tmp_path / "config.json"
58
+ cfg.write_text(
59
+ json.dumps(
60
+ {
61
+ "api_key": "mrxs_file_key_should_not_win",
62
+ "country": "usa",
63
+ "service": "whatsapp",
64
+ }
65
+ ),
66
+ encoding="utf-8",
67
+ )
68
+ monkeypatch.setenv("MRXSIM_API_KEY", "mrxs_env_key_wins_abcdef")
69
+ client = Client.from_config(cfg)
70
+ # Fingerprint should reflect env key prefix, not file key.
71
+ assert "mrxs_e" in repr(client)
72
+ client.close()
73
+
74
+
75
+ def test_parse_rate_limit() -> None:
76
+ response = MagicMock()
77
+ response.status_code = 429
78
+ response.json.return_value = {"detail": {"error": "slow down"}}
79
+ with pytest.raises(MrxsimRateLimitError):
80
+ Client._parse(response)
81
+
82
+
83
+ def test_parse_auth_error() -> None:
84
+ response = MagicMock()
85
+ response.status_code = 401
86
+ response.json.return_value = {"detail": "invalid key"}
87
+ with pytest.raises(MrxsimAuthError):
88
+ Client._parse(response)
89
+
90
+
91
+ def test_get_number_posts_payload(monkeypatch: pytest.MonkeyPatch) -> None:
92
+ monkeypatch.setenv("MRXSIM_API_KEY", "mrxs_unit_test_key_xyz")
93
+ client = Client(country="egypt", service="telegram")
94
+ mock_response = MagicMock()
95
+ mock_response.status_code = 200
96
+ mock_response.json.return_value = {
97
+ "id": "ord_1",
98
+ "phone_number": "+201000000000",
99
+ "price": "0.56",
100
+ "status": "PENDING",
101
+ }
102
+ with patch.object(client._session, "request", return_value=mock_response) as req:
103
+ order = client.get_number()
104
+ assert order["id"] == "ord_1"
105
+ kwargs = req.call_args.kwargs
106
+ assert kwargs["method"] == "POST"
107
+ assert kwargs["json"]["country"] == "egypt"
108
+ assert kwargs["json"]["service"] == "telegram"
109
+ client.close()
110
+
111
+
112
+ def test_get_number_missing_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
113
+ monkeypatch.setenv("MRXSIM_API_KEY", "mrxs_unit_test_key_xyz")
114
+ client = Client()
115
+ with pytest.raises(MrxsimConfigError):
116
+ client.get_number()
117
+ client.close()
118
+
119
+
120
+ def test_api_error_body(monkeypatch: pytest.MonkeyPatch) -> None:
121
+ monkeypatch.setenv("MRXSIM_API_KEY", "mrxs_unit_test_key_xyz")
122
+ response = MagicMock()
123
+ response.status_code = 422
124
+ response.json.return_value = {"detail": {"error": "no stock"}}
125
+ with pytest.raises(MrxsimAPIError) as excinfo:
126
+ Client._parse(response)
127
+ assert excinfo.value.status_code == 422