anymessage-sdk 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.
anymessage/__init__.py ADDED
@@ -0,0 +1,132 @@
1
+ # anymessage/__init__.py
2
+ """
3
+ anymessage — Python SDK for the service https://api.anymessage.shop
4
+
5
+ The library provides a high-level client `AnyMessageClient`
6
+ and convenient helpers for interacting with the API:
7
+
8
+ Short-term emails:
9
+ - order a temporary email inbox and wait for a message,
10
+ - reorder or cancel an activation,
11
+ - get the balance and available email inventory.
12
+
13
+ Long-term emails:
14
+ - buy persistent mailboxes (bulk up to 1000),
15
+ - fetch recent or all messages via the API,
16
+ - look up a purchased mailbox by address.
17
+
18
+ Activation Rate:
19
+ - get cancel statistics per site+domain pair,
20
+ - enable/disable automatic blocking on low ratio.
21
+
22
+ API docs: https://anymessage.shop/en/docs
23
+ """
24
+
25
+ __version__ = "0.2.0"
26
+
27
+ # Primary client
28
+ from .client import AnyMessageClient
29
+
30
+ # Errors (all inherit from AnyMessageError)
31
+ from .errors import (
32
+ AnyMessageError,
33
+ AuthError,
34
+ ValidationError,
35
+ NotFoundError,
36
+ NoBalanceError,
37
+ NoEmailsError,
38
+ ActivationCanceledError,
39
+ ActivationAlreadyCanceledError,
40
+ EmailBannedError,
41
+ WaitMessageError,
42
+ RatioBlockError,
43
+ )
44
+
45
+ # Data models (API responses)
46
+ from .models import (
47
+ OrderResponse,
48
+ QuantityResponse,
49
+ Message,
50
+ RatioEntry,
51
+ ImapCredentials,
52
+ LongliveEmail,
53
+ LongliveOrderResponse,
54
+ LongliveMessage,
55
+ )
56
+
57
+ # High-level API wrappers
58
+ from .methods import (
59
+ # Short-term email
60
+ get_balance,
61
+ get_email_quantity,
62
+ order_email,
63
+ reorder_email,
64
+ cancel_email,
65
+ get_message,
66
+ wait_for_message,
67
+ order_wait_and_extract,
68
+ # Activation rate
69
+ get_ratio,
70
+ enable_block_ratio,
71
+ disable_block_ratio,
72
+ # Long-live email
73
+ get_longlive_quantity,
74
+ order_longlive_email,
75
+ get_longlive_history,
76
+ get_longlive_last_messages,
77
+ get_longlive_messages,
78
+ find_longlive_email,
79
+ )
80
+
81
+ # Utilities
82
+ from .utils import extract_with_regex, domain_param
83
+
84
+ # Explicitly define the public package interface
85
+ __all__ = (
86
+ # Client
87
+ "AnyMessageClient",
88
+ # Errors
89
+ "AnyMessageError",
90
+ "AuthError",
91
+ "ValidationError",
92
+ "NotFoundError",
93
+ "NoBalanceError",
94
+ "NoEmailsError",
95
+ "ActivationCanceledError",
96
+ "ActivationAlreadyCanceledError",
97
+ "EmailBannedError",
98
+ "WaitMessageError",
99
+ "RatioBlockError",
100
+ # Models
101
+ "OrderResponse",
102
+ "QuantityResponse",
103
+ "Message",
104
+ "RatioEntry",
105
+ "ImapCredentials",
106
+ "LongliveEmail",
107
+ "LongliveOrderResponse",
108
+ "LongliveMessage",
109
+ # Short-term email methods
110
+ "get_balance",
111
+ "get_email_quantity",
112
+ "order_email",
113
+ "reorder_email",
114
+ "cancel_email",
115
+ "get_message",
116
+ "wait_for_message",
117
+ "order_wait_and_extract",
118
+ # Activation rate methods
119
+ "get_ratio",
120
+ "enable_block_ratio",
121
+ "disable_block_ratio",
122
+ # Long-live email methods
123
+ "get_longlive_quantity",
124
+ "order_longlive_email",
125
+ "get_longlive_history",
126
+ "get_longlive_last_messages",
127
+ "get_longlive_messages",
128
+ "find_longlive_email",
129
+ # Utilities
130
+ "extract_with_regex",
131
+ "domain_param",
132
+ )
anymessage/client.py ADDED
@@ -0,0 +1,383 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Dict, List, Optional, Callable, Union
5
+ import requests
6
+
7
+ from .config import DEFAULT_TIMEOUT
8
+ from .errors import AuthError
9
+
10
+
11
+ class AnyMessageClient:
12
+ """
13
+ High-level client for the AnyMessage API.
14
+
15
+ This client encapsulates:
16
+ • an authorization token,
17
+ • a reusable HTTP session (requests.Session),
18
+ • a default network timeout for API calls.
19
+
20
+ Recommended usage (context manager):
21
+
22
+ >>> from anymessage import AnyMessageClient
23
+ >>> with AnyMessageClient(token="YOUR_TOKEN") as client:
24
+ ... balance = client.get_balance()
25
+ ... order = client.order_email(site="instagram.com", domain="gmx")
26
+ ... msg = client.wait_for_message(id=order.id, timeout=120, poll_interval=2)
27
+
28
+ Thread safety:
29
+ • create a separate client instance per thread or task;
30
+ • the library does not use temporary files — everything runs in memory.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ token: str,
36
+ *,
37
+ session: Optional[requests.Session] = None,
38
+ timeout: int = DEFAULT_TIMEOUT,
39
+ ) -> None:
40
+ """
41
+ Initialize the client.
42
+
43
+ :param token: Required API authorization token.
44
+ :param session: Optional external requests.Session. If not provided, a new one is created.
45
+ :param timeout: Default network timeout (in seconds) for all requests.
46
+ :raises AuthError: If the token is missing.
47
+ """
48
+ if not token:
49
+ raise AuthError("Token must be provided")
50
+
51
+ self.token: str = token
52
+ self.timeout: int = timeout
53
+
54
+ # If no external session is provided, create our own and mark it for cleanup.
55
+ self._own_session: bool = session is None
56
+ self.session: requests.Session = session or requests.Session()
57
+
58
+ # Optionally attach retry adapters for better network stability.
59
+ try:
60
+ from .http import mount_retries # local import to avoid circular dependencies
61
+ mount_retries(self.session)
62
+ except Exception:
63
+ # Fallback: continue without retries if adapters are not available.
64
+ pass
65
+
66
+ # ────────────────────────── Context Manager ──────────────────────────
67
+
68
+ def __enter__(self) -> "AnyMessageClient":
69
+ return self
70
+
71
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
72
+ self.close()
73
+
74
+ def close(self) -> None:
75
+ """Explicitly close the HTTP session if it was created internally."""
76
+ if self._own_session and self.session:
77
+ self.session.close()
78
+
79
+ # ─────────────────────────── High-level API Methods ───────────────────────────
80
+
81
+ def get_balance(self) -> float:
82
+ """
83
+ Retrieve the current user balance.
84
+
85
+ Endpoint: GET /user/balance
86
+ :return: Balance as a float.
87
+ :raises ApiError/AuthError: On API or authentication errors.
88
+ """
89
+ from .methods.balance import _get_balance
90
+ return _get_balance(self.session, self.token, self.timeout)
91
+
92
+ def get_email_quantity(self, *, site: str):
93
+ """
94
+ Get available email data for a specific site.
95
+
96
+ Endpoint: GET /email/quantity
97
+ :param site: Target site (e.g., "instagram.com").
98
+ :return: QuantityResponse
99
+ :raises ValidationError: If `site` is not provided.
100
+ :raises ApiError/AuthError: On API or authentication errors.
101
+ """
102
+ from .methods.email import _get_email_quantity
103
+ return _get_email_quantity(self.session, self.token, self.timeout, site=site)
104
+
105
+ def order_email(
106
+ self,
107
+ *,
108
+ site: str,
109
+ domain: str, # may include multiple providers: "mailcom,gmx,hotmail"
110
+ regex: Optional[str] = None, # regex for extracting part of the message
111
+ subject: Optional[str] = None, # subject filter for incoming email
112
+ ):
113
+ """
114
+ Order a temporary email address for the given site/domain.
115
+
116
+ Endpoint: GET /email/order
117
+ :return: OrderResponse (contains id, email, etc.)
118
+ :raises ValidationError/NoEmailsError/NoBalanceError/ApiError/AuthError
119
+ """
120
+ from .methods.email import _order_email
121
+ return _order_email(
122
+ self.session,
123
+ self.token,
124
+ self.timeout,
125
+ site=site,
126
+ domain=domain,
127
+ regex=regex,
128
+ subject=subject,
129
+ )
130
+
131
+ def reorder_email(
132
+ self,
133
+ *,
134
+ id: Optional[Union[str, int]] = None,
135
+ email: Optional[str] = None,
136
+ site: Optional[str] = None,
137
+ ):
138
+ """
139
+ Reorder email (repeat activation).
140
+
141
+ Endpoint: GET /email/reorder
142
+ Pass either a previous activation `id`, or an `email` + `site` pair.
143
+
144
+ :return: OrderResponse
145
+ :raises ValidationError/ApiError/AuthError
146
+ """
147
+ from .methods.email import _reorder_email
148
+ return _reorder_email(
149
+ self.session,
150
+ self.token,
151
+ self.timeout,
152
+ id=id,
153
+ email=email,
154
+ site=site,
155
+ )
156
+
157
+ def cancel_email(self, *, id: Union[str, int]) -> str:
158
+ """
159
+ Cancel an email activation.
160
+
161
+ Endpoint: GET /email/cancel
162
+ :return: Confirmation text (e.g., "activation canceled").
163
+ :raises ApiError/AuthError
164
+ """
165
+ from .methods.email import _cancel_email
166
+ return _cancel_email(self.session, self.token, self.timeout, id=id)
167
+
168
+ def get_message(self, *, id: Union[str, int], preview: bool = False):
169
+ """
170
+ Retrieve an email message for a given activation.
171
+
172
+ Endpoint: GET /email/getmessage
173
+
174
+ API behavior:
175
+ • preview=False (default) — JSON response, HTML in `message` field;
176
+ • preview=True — raw HTML (no JSON wrapper).
177
+ The low-level function normalizes the response into a Message model.
178
+
179
+ :return: Message
180
+ :raises WaitMessageError: If the message is not yet ready (value == "wait message").
181
+ :raises ApiError/AuthError
182
+ """
183
+ from .methods.email import _get_message
184
+ return _get_message(self.session, self.token, self.timeout, id=id, preview=preview)
185
+
186
+ def wait_for_message(
187
+ self,
188
+ *,
189
+ id: Union[str, int],
190
+ timeout: Union[int, float] = 120,
191
+ poll_interval: Union[int, float] = 2,
192
+ preview: bool = False,
193
+ on_tick: Optional[Callable[[int], None]] = None,
194
+ stop_event: Optional["object"] = None, # e.g., threading.Event; use `object` type to avoid hard dependency
195
+ ):
196
+ """
197
+ Wait for a message to arrive and return it as a Message object.
198
+
199
+ Internally, it repeatedly polls GET /email/getmessage until the message arrives
200
+ or the overall timeout expires. Includes progress callback and cancellation support.
201
+
202
+ :param id: Activation ID.
203
+ :param timeout: Overall waiting limit in seconds.
204
+ :param poll_interval: Polling interval in seconds.
205
+ :param preview: Whether to use preview=1 mode.
206
+ :param on_tick: Optional progress callback: on_tick(iteration: int) -> None.
207
+ :param stop_event: Optional object with `is_set()` method to cancel waiting.
208
+ :return: Message
209
+ :raises WaitMessageError/ApiError/AuthError
210
+ """
211
+ from .methods.email import _wait_for_message
212
+ return _wait_for_message(
213
+ self.session,
214
+ self.token,
215
+ self.timeout,
216
+ id=id,
217
+ overall_timeout=timeout, # corresponds to parameter name in the low-level function
218
+ poll_interval=poll_interval,
219
+ preview=preview,
220
+ on_tick=on_tick,
221
+ stop_event=stop_event,
222
+ )
223
+
224
+ def order_wait_and_extract(
225
+ self,
226
+ *,
227
+ site: str,
228
+ domain: Union[str, list],
229
+ regex: Optional[str] = None,
230
+ subject: Optional[str] = None,
231
+ timeout: Union[int, float] = 180,
232
+ poll_interval: Union[int, float] = 2.0,
233
+ preview: bool = False,
234
+ stop_event: Optional[object] = None,
235
+ ):
236
+ """
237
+ Convenience method: order an email → wait for message → extract via regex.
238
+
239
+ :return: Tuple (activation_id, email, html_or_text, extracted_value_or_None)
240
+ """
241
+ from .methods.email import order_wait_and_extract as _owe
242
+ return _owe(
243
+ self, site=site, domain=domain, regex=regex, subject=subject,
244
+ timeout=timeout, poll_interval=poll_interval, preview=preview,
245
+ stop_event=stop_event,
246
+ )
247
+
248
+ # ────────────────────────── Activation Rate ──────────────────────────
249
+
250
+ def get_ratio(self, *, full: bool = False):
251
+ """
252
+ Get activation rate statistics per site+domain pair.
253
+
254
+ Endpoint: GET /user/getratio
255
+ :param full: If True, include pairs with cancel_percent == 0 (ratio >= 40%).
256
+ :return: List[RatioEntry]
257
+ :raises AuthError: On authentication errors.
258
+ """
259
+ from .methods.ratio import _get_ratio
260
+ return _get_ratio(self.session, self.token, self.timeout, full=full)
261
+
262
+ def enable_block_ratio(self) -> bool:
263
+ """
264
+ Enable automatic blocking when activation rate drops below threshold.
265
+
266
+ Endpoint: GET /user/enableBlockRatio
267
+ :return: True on success.
268
+ """
269
+ from .methods.ratio import _enable_block_ratio
270
+ return _enable_block_ratio(self.session, self.token, self.timeout)
271
+
272
+ def disable_block_ratio(self) -> bool:
273
+ """
274
+ Disable automatic ratio blocking.
275
+
276
+ Endpoint: GET /user/disableBlockRatio
277
+ :return: True on success.
278
+ """
279
+ from .methods.ratio import _disable_block_ratio
280
+ return _disable_block_ratio(self.session, self.token, self.timeout)
281
+
282
+ # ────────────────────────── Long-live Emails ──────────────────────────
283
+
284
+ def get_longlive_quantity(self, *, site: str):
285
+ """
286
+ Get available long-term email domains and counts for a site.
287
+
288
+ Endpoint: GET /longlive-email/quantity
289
+ :param site: Target site (e.g., "instagram.com").
290
+ :return: QuantityResponse — data maps domain → {count, price}.
291
+ :raises ValidationError/AuthError
292
+ """
293
+ from .methods.longlive import _get_longlive_quantity
294
+ return _get_longlive_quantity(self.session, self.token, self.timeout, site=site)
295
+
296
+ def order_longlive_email(self, *, site: str, domain: str, count: int = 1):
297
+ """
298
+ Purchase one or more long-term mailboxes (bulk up to 1000).
299
+
300
+ Endpoint: GET /longlive-email/order
301
+ :param site: Target site (e.g., "instagram.com").
302
+ :param domain: Mailbox domain (e.g., "hotmail.com").
303
+ :param count: How many to buy (1–1000).
304
+ :return: LongliveOrderResponse
305
+ :raises ValidationError/NoEmailsError/NoBalanceError/AuthError
306
+ """
307
+ from .methods.longlive import _order_longlive_email
308
+ return _order_longlive_email(
309
+ self.session, self.token, self.timeout,
310
+ site=site, domain=domain, count=count,
311
+ )
312
+
313
+ def get_longlive_history(self, *, offset: int = 1, limit: int = 10) -> Dict[str, Any]:
314
+ """
315
+ Get long-term email order history.
316
+
317
+ Endpoint: GET /longlive-email/history
318
+ :param offset: Pagination offset (1-based).
319
+ :param limit: Number of records to return.
320
+ :return: Dict keyed by activation ID.
321
+ :raises AuthError
322
+ """
323
+ from .methods.longlive import _get_longlive_history
324
+ return _get_longlive_history(
325
+ self.session, self.token, self.timeout,
326
+ offset=offset, limit=limit,
327
+ )
328
+
329
+ def get_longlive_last_messages(
330
+ self,
331
+ *,
332
+ id: Union[str, int],
333
+ subject: Optional[str] = None,
334
+ ):
335
+ """
336
+ Get messages received in the last 40 minutes for a long-term activation.
337
+
338
+ Endpoint: GET /longlive-email/getlastmessages
339
+ :param id: Activation ID.
340
+ :param subject: Optional subject filter.
341
+ :return: List[LongliveMessage]
342
+ :raises ValidationError/NotFoundError/AuthError
343
+ """
344
+ from .methods.longlive import _get_longlive_last_messages
345
+ return _get_longlive_last_messages(
346
+ self.session, self.token, self.timeout,
347
+ id=id, subject=subject,
348
+ )
349
+
350
+ def get_longlive_messages(
351
+ self,
352
+ *,
353
+ id: Union[str, int],
354
+ created_at: Optional[int] = None,
355
+ subject: Optional[str] = None,
356
+ ):
357
+ """
358
+ Get all messages for a long-term activation.
359
+
360
+ Endpoint: GET /longlive-email/getmessages
361
+ :param id: Activation ID.
362
+ :param created_at: Optional Unix timestamp to return only newer messages.
363
+ :param subject: Optional subject filter.
364
+ :return: List[LongliveMessage]
365
+ :raises ValidationError/NotFoundError/AuthError
366
+ """
367
+ from .methods.longlive import _get_longlive_messages
368
+ return _get_longlive_messages(
369
+ self.session, self.token, self.timeout,
370
+ id=id, created_at=created_at, subject=subject,
371
+ )
372
+
373
+ def find_longlive_email(self, *, email: str) -> List[Dict[str, Any]]:
374
+ """
375
+ Find a previously purchased long-term mailbox by its email address.
376
+
377
+ Endpoint: GET /longlive-email/findemail
378
+ :param email: Full mailbox address (e.g., "example@outlook.com").
379
+ :return: List of activation records; empty list if not found.
380
+ :raises ValidationError/AuthError
381
+ """
382
+ from .methods.longlive import _find_longlive_email
383
+ return _find_longlive_email(self.session, self.token, self.timeout, email=email)
anymessage/config.py ADDED
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ config.py — basic configuration for the anymessage SDK
4
+ """
5
+
6
+ from __future__ import annotations
7
+ from dataclasses import dataclass
8
+ from typing import Dict
9
+
10
+ BASE_URL: str = "https://api.anymessage.shop"
11
+ DEFAULT_TIMEOUT: int = 30
12
+
13
+ @dataclass(frozen=True)
14
+ class SDKInfo:
15
+ name: str = "anymessage-python"
16
+ version: str = "0.2.0"
17
+
18
+ SDK_INFO = SDKInfo()
19
+
20
+ def get_soft_header() -> Dict[str, str]:
21
+ return {
22
+ "X-Soft-Id": "126",
23
+ "X-SDK-Name": SDK_INFO.name,
24
+ "X-SDK-Version": SDK_INFO.version,
25
+ }
26
+
27
+ DEBUG_HTTP: bool = True
anymessage/errors.py ADDED
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ errors.py — exception definitions for the anymessage SDK
4
+
5
+ All client exceptions inherit from the base class AnyMessageError.
6
+ Each specific error corresponds to a particular API error value,
7
+ for example: {"status":"error","value":"token"} → AuthError.
8
+ """
9
+
10
+
11
+ class AnyMessageError(Exception):
12
+ """Base exception for all SDK errors."""
13
+
14
+
15
+ # --- Authorization / Parameter Errors ----------------------------------------
16
+
17
+ class AuthError(AnyMessageError):
18
+ """Invalid or missing API token."""
19
+
20
+
21
+ class ValidationError(AnyMessageError):
22
+ """Invalid or missing parameter (e.g., site, domain)."""
23
+
24
+
25
+ # --- Activation-related Errors ----------------------------------------------
26
+
27
+ class NotFoundError(AnyMessageError):
28
+ """Activation or resource not found."""
29
+
30
+
31
+ class NoBalanceError(AnyMessageError):
32
+ """Insufficient balance."""
33
+
34
+
35
+ class NoEmailsError(AnyMessageError):
36
+ """No available emails for the given site or domain."""
37
+
38
+
39
+ class ActivationCanceledError(AnyMessageError):
40
+ """Activation has been canceled by the user."""
41
+
42
+
43
+ class ActivationAlreadyCanceledError(AnyMessageError):
44
+ """Activation has already been canceled."""
45
+
46
+
47
+ class EmailBannedError(AnyMessageError):
48
+ """This email address is banned by the service."""
49
+
50
+
51
+ class RatioBlockError(AnyMessageError):
52
+ """Blocked due to low activation rate for this site+domain pair."""
53
+
54
+
55
+ # --- Message Waiting --------------------------------------------------------
56
+
57
+ class WaitMessageError(AnyMessageError):
58
+ """Message not yet available (API returned 'wait message')."""
59
+
60
+
61
+ # --- Mapping API 'value' field to exceptions --------------------------------
62
+
63
+ ERROR_VALUE_TO_EXCEPTION = {
64
+ "token": AuthError,
65
+ "site": ValidationError,
66
+ "domain": ValidationError,
67
+ "no activation": NotFoundError,
68
+ "activation canceled": ActivationCanceledError,
69
+ "activation already canceled": ActivationAlreadyCanceledError,
70
+ "no balance": NoBalanceError,
71
+ "no emails": NoEmailsError,
72
+ "email banned": EmailBannedError,
73
+ "wait message": WaitMessageError,
74
+ "ratio block": RatioBlockError,
75
+ }
76
+
77
+
78
+ def map_api_error(value: str) -> AnyMessageError:
79
+ """
80
+ Map the API "value" field to a corresponding exception class.
81
+
82
+ Example:
83
+ >>> map_api_error("token")
84
+ AuthError('token')
85
+
86
+ :param value: String value from the API response field "value".
87
+ :return: Instance of the corresponding exception.
88
+ """
89
+ exc_cls = ERROR_VALUE_TO_EXCEPTION.get(value, AnyMessageError)
90
+ return exc_cls(value)