some random stuff. caelestia incoming
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""Async ntfy client library."""
|
||||
|
||||
from .ntfy import Ntfy
|
||||
from .types import (
|
||||
Account,
|
||||
AccountBilling,
|
||||
AccountLimits,
|
||||
AccountStats,
|
||||
AccountTier,
|
||||
AccountTokenResponse,
|
||||
Attachment,
|
||||
BroadcastAction,
|
||||
DeleteAfter,
|
||||
Event,
|
||||
Everyone,
|
||||
HttpAction,
|
||||
Message,
|
||||
Notification,
|
||||
Priority,
|
||||
Reservation,
|
||||
Response,
|
||||
Sound,
|
||||
Stats,
|
||||
ViewAction,
|
||||
)
|
||||
|
||||
__version__ = "0.0.0"
|
||||
|
||||
__all__ = [
|
||||
"Account",
|
||||
"AccountBilling",
|
||||
"AccountLimits",
|
||||
"AccountStats",
|
||||
"AccountTier",
|
||||
"AccountTokenResponse",
|
||||
"Attachment",
|
||||
"BroadcastAction",
|
||||
"DeleteAfter",
|
||||
"Event",
|
||||
"Everyone",
|
||||
"HttpAction",
|
||||
"Message",
|
||||
"Notification",
|
||||
"Ntfy",
|
||||
"Priority",
|
||||
"Reservation",
|
||||
"Response",
|
||||
"Sound",
|
||||
"Stats",
|
||||
"ViewAction",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
"""Constants for aiontfy."""
|
||||
|
||||
__version__ = "0.5.4"
|
||||
|
||||
MIN_PRIORITY = 1
|
||||
MAX_PRIORITY = 5
|
||||
@@ -0,0 +1,496 @@
|
||||
"""Exceptions for aiontfy."""
|
||||
|
||||
|
||||
class NtfyException(Exception): # noqa: N818
|
||||
"""Base ntfy exception."""
|
||||
|
||||
|
||||
class NtfyHTTPError(NtfyException):
|
||||
"""Base class for HTTP errors."""
|
||||
|
||||
def __init__(
|
||||
self, code: int, http: int, error: str, link: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the exception with a code, HTTP status, error message, and link.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
The error code.
|
||||
http : int
|
||||
The HTTP status code.
|
||||
error : str
|
||||
The error message.
|
||||
link : str, optional
|
||||
A link to more information about the error.
|
||||
|
||||
"""
|
||||
self.code = code
|
||||
self.http = http
|
||||
self.error = error
|
||||
self.link = link
|
||||
|
||||
super().__init__(self.error)
|
||||
|
||||
|
||||
class NtfyConnectionError(NtfyException):
|
||||
"""Connection error."""
|
||||
|
||||
|
||||
class NtfyTimeoutError(NtfyException):
|
||||
"""Timeout error."""
|
||||
|
||||
|
||||
class NtfyUnknownError(NtfyException):
|
||||
"""Unexpected HTTP errors."""
|
||||
|
||||
|
||||
class NtfyBadRequestError(NtfyHTTPError):
|
||||
"""400 Bad Request."""
|
||||
|
||||
|
||||
class NtfyUnauthorizedError(NtfyHTTPError):
|
||||
"""401 Unauthorized."""
|
||||
|
||||
|
||||
class NtfyForbiddenError(NtfyHTTPError):
|
||||
"""403 Forbidden."""
|
||||
|
||||
|
||||
class NtfyNotFoundError(NtfyHTTPError):
|
||||
"""404 Not Found."""
|
||||
|
||||
|
||||
class NtfyConflictError(NtfyHTTPError):
|
||||
"""409 Conflict."""
|
||||
|
||||
|
||||
class NtfyGoneError(NtfyHTTPError):
|
||||
"""410 Gone."""
|
||||
|
||||
|
||||
class NtfyRequestEntityTooLargeError(NtfyHTTPError):
|
||||
"""413 Request Entity Too Large."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsError(NtfyHTTPError):
|
||||
"""429 Too Many Requests."""
|
||||
|
||||
|
||||
class NtfyInternalServerError(NtfyHTTPError):
|
||||
"""500 Internal Server Error."""
|
||||
|
||||
|
||||
class NtfyInsufficientStorageError(NtfyHTTPError):
|
||||
"""507 Insufficient Storage."""
|
||||
|
||||
|
||||
class NtfyBadRequestEmailDisabledError(NtfyBadRequestError):
|
||||
"""40001 E-mail notifications are not enabled."""
|
||||
|
||||
|
||||
class NtfyBadRequestDelayNoCacheError(NtfyBadRequestError):
|
||||
"""40002 Cannot disable cache for delayed message."""
|
||||
|
||||
|
||||
class NtfyBadRequestDelayNoEmailError(NtfyBadRequestError):
|
||||
"""40003 Delayed e-mail notifications are not supported."""
|
||||
|
||||
|
||||
class NtfyBadRequestDelayCannotParseError(NtfyBadRequestError):
|
||||
"""40004 Invalid delay parameter: unable to parse delay."""
|
||||
|
||||
|
||||
class NtfyBadRequestDelayTooSmallError(NtfyBadRequestError):
|
||||
"""40005 Invalid delay parameter: too small."""
|
||||
|
||||
|
||||
class NtfyBadRequestDelayTooLargeError(NtfyBadRequestError):
|
||||
"""40006 Invalid delay parameter: too large."""
|
||||
|
||||
|
||||
class NtfyBadRequestPriorityInvalidError(NtfyBadRequestError):
|
||||
"""40007 Invalid priority parameter."""
|
||||
|
||||
|
||||
class NtfyBadRequestSinceInvalidError(NtfyBadRequestError):
|
||||
"""40008 Invalid since parameter."""
|
||||
|
||||
|
||||
class NtfyBadRequestTopicInvalidError(NtfyBadRequestError):
|
||||
"""40009 Invalid request: topic invalid."""
|
||||
|
||||
|
||||
class NtfyBadRequestTopicDisallowedError(NtfyBadRequestError):
|
||||
"""40010 Invalid request: topic name is not allowed."""
|
||||
|
||||
|
||||
class NtfyBadRequestMessageNotUTF8Error(NtfyBadRequestError):
|
||||
"""40011 Invalid request: message must be UTF-8 encoded."""
|
||||
|
||||
|
||||
class NtfyBadRequestAttachmentURLInvalidError(NtfyBadRequestError):
|
||||
"""40013 Invalid request: attachment URL is invalid."""
|
||||
|
||||
|
||||
class NtfyBadRequestAttachmentsDisallowedError(NtfyBadRequestError):
|
||||
"""40014 Invalid request: attachments not allowed."""
|
||||
|
||||
|
||||
class NtfyBadRequestAttachmentsExpiryBeforeDeliveryError(NtfyBadRequestError):
|
||||
"""40015 Invalid request: attachment expiry before delayed delivery date."""
|
||||
|
||||
|
||||
class NtfyBadRequestWebSocketsUpgradeHeaderMissingError(NtfyBadRequestError):
|
||||
"""40016 Invalid request: client not using the websocket protocol."""
|
||||
|
||||
|
||||
class NtfyBadRequestMessageJSONInvalidError(NtfyBadRequestError):
|
||||
"""40017 Invalid request: request body must be message JSON."""
|
||||
|
||||
|
||||
class NtfyBadRequestActionsInvalidError(NtfyBadRequestError):
|
||||
"""40018 Invalid request: actions invalid."""
|
||||
|
||||
|
||||
class NtfyBadRequestMatrixMessageInvalidError(NtfyBadRequestError):
|
||||
"""40019 Invalid request: Matrix JSON invalid."""
|
||||
|
||||
|
||||
class NtfyBadRequestIconURLInvalidError(NtfyBadRequestError):
|
||||
"""40021 Invalid request: icon URL is invalid."""
|
||||
|
||||
|
||||
class NtfyBadRequestSignupNotEnabledError(NtfyBadRequestError):
|
||||
"""40022 Invalid request: signup not enabled."""
|
||||
|
||||
|
||||
class NtfyBadRequestNoTokenProvidedError(NtfyBadRequestError):
|
||||
"""40023 Invalid request: no token provided."""
|
||||
|
||||
|
||||
class NtfyBadRequestJSONInvalidError(NtfyBadRequestError):
|
||||
"""40024 Invalid request: request body must be valid JSON."""
|
||||
|
||||
|
||||
class NtfyBadRequestPermissionInvalidError(NtfyBadRequestError):
|
||||
"""40025 Invalid request: incorrect permission string."""
|
||||
|
||||
|
||||
class NtfyBadRequestIncorrectwordConfirmationError(NtfyBadRequestError):
|
||||
"""40026 Invalid request: word confirmation is not correct."""
|
||||
|
||||
|
||||
class NtfyBadRequestNotAPaidUserError(NtfyBadRequestError):
|
||||
"""40027 Invalid request: not a paid user."""
|
||||
|
||||
|
||||
class NtfyBadRequestBillingRequestInvalidError(NtfyBadRequestError):
|
||||
"""40028 Invalid request: not a valid billing request."""
|
||||
|
||||
|
||||
class NtfyBadRequestBillingSubscriptionExistsError(NtfyBadRequestError):
|
||||
"""40029 Invalid request: billing subscription already exists."""
|
||||
|
||||
|
||||
class NtfyBadRequestTierInvalidError(NtfyBadRequestError):
|
||||
"""40030 Invalid request: tier does not exist."""
|
||||
|
||||
|
||||
class NtfyBadRequestUserNotFoundError(NtfyBadRequestError):
|
||||
"""40031 Invalid request: user does not exist."""
|
||||
|
||||
|
||||
class NtfyBadRequestPhoneCallsDisabledError(NtfyBadRequestError):
|
||||
"""40032 Invalid request: calling is disabled."""
|
||||
|
||||
|
||||
class NtfyBadRequestPhoneNumberInvalidError(NtfyBadRequestError):
|
||||
"""40033 Invalid request: phone number invalid."""
|
||||
|
||||
|
||||
class NtfyBadRequestPhoneNumberNotVerifiedError(NtfyBadRequestError):
|
||||
"""40034 Invalid request: phone number not verified."""
|
||||
|
||||
|
||||
class NtfyBadRequestAnonymousCallsNotAllowedError(NtfyBadRequestError):
|
||||
"""40035 Invalid request: anonymous phone calls are not allowed."""
|
||||
|
||||
|
||||
class NtfyBadRequestPhoneNumberVerifyChannelInvalidError(NtfyBadRequestError):
|
||||
"""40036 Invalid request: verification channel must be 'sms' or 'call'."""
|
||||
|
||||
|
||||
class NtfyBadRequestDelayNoCallError(NtfyBadRequestError):
|
||||
"""40037 Invalid request: delayed call notifications are not supported."""
|
||||
|
||||
|
||||
class NtfyBadRequestWebPushSubscriptionInvalidError(NtfyBadRequestError):
|
||||
"""40038 Invalid request: web push payload malformed."""
|
||||
|
||||
|
||||
class NtfyBadRequestWebPushEndpointUnknownError(NtfyBadRequestError):
|
||||
"""40039 Invalid request: web push endpoint unknown."""
|
||||
|
||||
|
||||
class NtfyBadRequestWebPushTopicCountTooHighError(NtfyBadRequestError):
|
||||
"""40040 Invalid request: too many web push topic subscriptions."""
|
||||
|
||||
|
||||
class NtfyBadRequestTemplateMessageTooLargeError(NtfyBadRequestError):
|
||||
"""40041 Invalid request: message or title is too large after replacing template."""
|
||||
|
||||
|
||||
class NtfyBadRequestTemplateMessageNotJSONError(NtfyBadRequestError):
|
||||
"""40042 Invalid request: message body must be JSON if templating is enabled."""
|
||||
|
||||
|
||||
class NtfyBadRequestTemplateInvalidError(NtfyBadRequestError):
|
||||
"""40043 Invalid request: could not parse template."""
|
||||
|
||||
|
||||
class NtfyBadRequestTemplateDisallowedFunctionCallsError(NtfyBadRequestError):
|
||||
"""40044 Invalid request: template contains disallowed function calls."""
|
||||
|
||||
|
||||
class NtfyBadRequestTemplateExecuteFailedError(NtfyBadRequestError):
|
||||
"""40045 Invalid request: template execution failed."""
|
||||
|
||||
|
||||
class NtfyBadRequestInvalidUsernameError(NtfyBadRequestError):
|
||||
"""40046 Invalid request: invalid username."""
|
||||
|
||||
|
||||
# 404 Not Found Errors
|
||||
class NtfyNotFoundPageError(NtfyNotFoundError):
|
||||
"""40401 Page not found."""
|
||||
|
||||
|
||||
# 401 Unauthorized Errors
|
||||
class NtfyUnauthorizedAuthenticationError(NtfyUnauthorizedError):
|
||||
"""40101 Unauthorized."""
|
||||
|
||||
|
||||
# 403 Forbidden Errors
|
||||
class NtfyForbiddenAccessError(NtfyForbiddenError):
|
||||
"""40301 Forbidden."""
|
||||
|
||||
|
||||
# 409 Conflict Errors
|
||||
class NtfyConflictUserExistsError(NtfyConflictError):
|
||||
"""40901 Conflict: user already exists."""
|
||||
|
||||
|
||||
class NtfyConflictTopicReservedError(NtfyConflictError):
|
||||
"""40902 Conflict: access control entry for topic or topic pattern already exists."""
|
||||
|
||||
|
||||
class NtfyConflictSubscriptionExistsError(NtfyConflictError):
|
||||
"""40903 Conflict: topic subscription already exists."""
|
||||
|
||||
|
||||
class NtfyConflictPhoneNumberExistsError(NtfyConflictError):
|
||||
"""40904 Conflict: phone number already exists."""
|
||||
|
||||
|
||||
# 410 Gone Errors
|
||||
class NtfyGonePhoneVerificationExpiredError(NtfyGoneError):
|
||||
"""41001 Phone number verification expired or does not exist."""
|
||||
|
||||
|
||||
# 413 Request Entity Too Large Errors
|
||||
class NtfyRequestEntityTooLargeAttachmentError(NtfyRequestEntityTooLargeError):
|
||||
"""41301 Attachment too large, or bandwidth limit reached."""
|
||||
|
||||
|
||||
class NtfyRequestEntityTooLargeMatrixRequestError(NtfyRequestEntityTooLargeError):
|
||||
"""41302 Matrix request is larger than the max allowed length."""
|
||||
|
||||
|
||||
class NtfyRequestEntityTooLargeJSONBodyError(NtfyRequestEntityTooLargeError):
|
||||
"""41303 JSON body too large."""
|
||||
|
||||
|
||||
# 429 Too Many Requests Errors
|
||||
class NtfyTooManyRequestsLimitRequestsError(NtfyTooManyRequestsError):
|
||||
"""42901 Limit reached: too many requests."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitEmailsError(NtfyTooManyRequestsError):
|
||||
"""42902 Limit reached: too many emails."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitSubscriptionsError(NtfyTooManyRequestsError):
|
||||
"""42903 Limit reached: too many active subscriptions."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitTotalTopicsError(NtfyTooManyRequestsError):
|
||||
"""42904 Limit reached: the total number of topics on the server has been reached."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitAttachmentBandwidthError(NtfyTooManyRequestsError):
|
||||
"""42905 Limit reached: daily bandwidth reached."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitAccountCreationError(NtfyTooManyRequestsError):
|
||||
"""42906 Limit reached: too many accounts created."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitReservationsError(NtfyTooManyRequestsError):
|
||||
"""42907 Limit reached: too many topic reservations for this user."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitMessagesError(NtfyTooManyRequestsError):
|
||||
"""42908 Limit reached: daily message quota reached."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitAuthFailureError(NtfyTooManyRequestsError):
|
||||
"""42909 Limit reached: too many auth failures."""
|
||||
|
||||
|
||||
class NtfyTooManyRequestsLimitCallsError(NtfyTooManyRequestsError):
|
||||
"""42910 Limit reached: daily phone call quota reached."""
|
||||
|
||||
|
||||
# 500 Internal Server Error
|
||||
class NtfyInternalErrorInvalidPathError(NtfyInternalServerError):
|
||||
"""50002 Internal server error: invalid path."""
|
||||
|
||||
|
||||
class NtfyInternalErrorMissingBaseURLError(NtfyInternalServerError):
|
||||
"""50003 Internal server error: base-url must be configured for this feature."""
|
||||
|
||||
|
||||
class NtfyInternalErrorWebPushUnableToPublishError(NtfyInternalServerError):
|
||||
"""50004 Internal server error: unable to publish web push message."""
|
||||
|
||||
|
||||
# 507 Insufficient Storage Errors
|
||||
class NtfyInsufficientStorageUnifiedPushError(NtfyInsufficientStorageError):
|
||||
"""50701 Cannot publish to UnifiedPush topic without previously active subscriber."""
|
||||
|
||||
|
||||
def raise_http_error(code: int, http: int, error: str, link: str | None = None) -> None:
|
||||
"""Raise an appropriate HTTP error based on the provided error code.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
The specific error code to raise.
|
||||
http : int
|
||||
The HTTP status code associated with the error.
|
||||
error : str
|
||||
A description of the error.
|
||||
link : str, optional
|
||||
A URL link providing more information about the error.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyBadRequestError
|
||||
If the error code is 400.
|
||||
NtfyUnauthorizedError
|
||||
If the error code is 401.
|
||||
NtfyForbiddenError
|
||||
If the error code is 403.
|
||||
NtfyNotFoundError
|
||||
If the error code is 404.
|
||||
NtfyConflictError
|
||||
If the error code is 409.
|
||||
NtfyGoneError
|
||||
If the error code is 410.
|
||||
NtfyRequestEntityTooLargeError
|
||||
If the error code is 413.
|
||||
NtfyTooManyRequestsError
|
||||
If the error code is 429.
|
||||
NtfyInternalServerError
|
||||
If the error code is 500.
|
||||
NtfyInsufficientStorageError
|
||||
If the error code is 507.
|
||||
NtfyUnknownError
|
||||
If the error code is not recognized.
|
||||
"""
|
||||
error_map = {
|
||||
400: NtfyBadRequestError,
|
||||
401: NtfyUnauthorizedError,
|
||||
403: NtfyForbiddenError,
|
||||
404: NtfyNotFoundError,
|
||||
409: NtfyConflictError,
|
||||
410: NtfyGoneError,
|
||||
413: NtfyRequestEntityTooLargeError,
|
||||
429: NtfyTooManyRequestsError,
|
||||
500: NtfyInternalServerError,
|
||||
507: NtfyInsufficientStorageError,
|
||||
40001: NtfyBadRequestEmailDisabledError,
|
||||
40002: NtfyBadRequestDelayNoCacheError,
|
||||
40003: NtfyBadRequestDelayNoEmailError,
|
||||
40004: NtfyBadRequestDelayCannotParseError,
|
||||
40005: NtfyBadRequestDelayTooSmallError,
|
||||
40006: NtfyBadRequestDelayTooLargeError,
|
||||
40007: NtfyBadRequestPriorityInvalidError,
|
||||
40008: NtfyBadRequestSinceInvalidError,
|
||||
40009: NtfyBadRequestTopicInvalidError,
|
||||
40010: NtfyBadRequestTopicDisallowedError,
|
||||
40011: NtfyBadRequestMessageNotUTF8Error,
|
||||
40013: NtfyBadRequestAttachmentURLInvalidError,
|
||||
40014: NtfyBadRequestAttachmentsDisallowedError,
|
||||
40015: NtfyBadRequestAttachmentsExpiryBeforeDeliveryError,
|
||||
40016: NtfyBadRequestWebSocketsUpgradeHeaderMissingError,
|
||||
40017: NtfyBadRequestMessageJSONInvalidError,
|
||||
40018: NtfyBadRequestActionsInvalidError,
|
||||
40019: NtfyBadRequestMatrixMessageInvalidError,
|
||||
40021: NtfyBadRequestIconURLInvalidError,
|
||||
40022: NtfyBadRequestSignupNotEnabledError,
|
||||
40023: NtfyBadRequestNoTokenProvidedError,
|
||||
40024: NtfyBadRequestJSONInvalidError,
|
||||
40025: NtfyBadRequestPermissionInvalidError,
|
||||
40026: NtfyBadRequestIncorrectwordConfirmationError,
|
||||
40027: NtfyBadRequestNotAPaidUserError,
|
||||
40028: NtfyBadRequestBillingRequestInvalidError,
|
||||
40029: NtfyBadRequestBillingSubscriptionExistsError,
|
||||
40030: NtfyBadRequestTierInvalidError,
|
||||
40031: NtfyBadRequestUserNotFoundError,
|
||||
40032: NtfyBadRequestPhoneCallsDisabledError,
|
||||
40033: NtfyBadRequestPhoneNumberInvalidError,
|
||||
40034: NtfyBadRequestPhoneNumberNotVerifiedError,
|
||||
40035: NtfyBadRequestAnonymousCallsNotAllowedError,
|
||||
40036: NtfyBadRequestPhoneNumberVerifyChannelInvalidError,
|
||||
40037: NtfyBadRequestDelayNoCallError,
|
||||
40038: NtfyBadRequestWebPushSubscriptionInvalidError,
|
||||
40039: NtfyBadRequestWebPushEndpointUnknownError,
|
||||
40040: NtfyBadRequestWebPushTopicCountTooHighError,
|
||||
40041: NtfyBadRequestTemplateMessageTooLargeError,
|
||||
40042: NtfyBadRequestTemplateMessageNotJSONError,
|
||||
40043: NtfyBadRequestTemplateInvalidError,
|
||||
40044: NtfyBadRequestTemplateDisallowedFunctionCallsError,
|
||||
40045: NtfyBadRequestTemplateExecuteFailedError,
|
||||
40046: NtfyBadRequestInvalidUsernameError,
|
||||
40401: NtfyNotFoundPageError,
|
||||
40101: NtfyUnauthorizedAuthenticationError,
|
||||
40301: NtfyForbiddenAccessError,
|
||||
40901: NtfyConflictUserExistsError,
|
||||
40902: NtfyConflictTopicReservedError,
|
||||
40903: NtfyConflictSubscriptionExistsError,
|
||||
40904: NtfyConflictPhoneNumberExistsError,
|
||||
41001: NtfyGonePhoneVerificationExpiredError,
|
||||
41301: NtfyRequestEntityTooLargeAttachmentError,
|
||||
41302: NtfyRequestEntityTooLargeMatrixRequestError,
|
||||
41303: NtfyRequestEntityTooLargeJSONBodyError,
|
||||
42901: NtfyTooManyRequestsLimitRequestsError,
|
||||
42902: NtfyTooManyRequestsLimitEmailsError,
|
||||
42903: NtfyTooManyRequestsLimitSubscriptionsError,
|
||||
42904: NtfyTooManyRequestsLimitTotalTopicsError,
|
||||
42905: NtfyTooManyRequestsLimitAttachmentBandwidthError,
|
||||
42906: NtfyTooManyRequestsLimitAccountCreationError,
|
||||
42907: NtfyTooManyRequestsLimitReservationsError,
|
||||
42908: NtfyTooManyRequestsLimitMessagesError,
|
||||
42909: NtfyTooManyRequestsLimitAuthFailureError,
|
||||
42910: NtfyTooManyRequestsLimitCallsError,
|
||||
50002: NtfyInternalErrorInvalidPathError,
|
||||
50003: NtfyInternalErrorMissingBaseURLError,
|
||||
50004: NtfyInternalErrorWebPushUnableToPublishError,
|
||||
50701: NtfyInsufficientStorageUnifiedPushError,
|
||||
}
|
||||
if error_class := error_map.get(code, error_map.get(http)):
|
||||
raise error_class(code, http, error, link)
|
||||
raise NtfyUnknownError
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Helpers for the aiontfy package."""
|
||||
|
||||
import platform
|
||||
|
||||
from aiohttp import __version__ as aiohttp_version
|
||||
|
||||
from .const import __version__
|
||||
|
||||
|
||||
def get_user_agent() -> str:
|
||||
"""Generate User-Agent string.
|
||||
|
||||
The User-Agent string contains details about the operating system,
|
||||
its version, architecture, the aiontfy version, aiohttp version,
|
||||
and Python version.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
A User-Agent string with OS details, library versions, and a project URL.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> client.get_user_agent()
|
||||
'aiontfy/0.0.0 (Windows 11 (10.0.22000); 64bit)
|
||||
aiohttp/3.10.9 Python/3.12.7 +https://github.com/tr4nt0r/aiontfy')'
|
||||
|
||||
"""
|
||||
os_name = platform.system()
|
||||
os_version = platform.version()
|
||||
os_release = platform.release()
|
||||
arch, _ = platform.architecture()
|
||||
os_info = f"{os_name} {os_release} ({os_version}); {arch}"
|
||||
|
||||
return (
|
||||
f"aiontfy/{__version__} ({os_info}) "
|
||||
f"aiohttp/{aiohttp_version} Python/{platform.python_version()} "
|
||||
" +https://github.com/tr4nt0r/aiontfy)"
|
||||
)
|
||||
@@ -0,0 +1,366 @@
|
||||
"""Async ntfy client library."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Self
|
||||
|
||||
from aiohttp import BasicAuth, ClientError, ClientSession, WSMsgType
|
||||
from yarl import URL
|
||||
|
||||
from .exceptions import NtfyConnectionError, NtfyTimeoutError, raise_http_error
|
||||
from .helpers import get_user_agent
|
||||
from .types import (
|
||||
Account,
|
||||
AccountTokenResponse,
|
||||
Everyone,
|
||||
Message,
|
||||
Notification,
|
||||
Response,
|
||||
Stats,
|
||||
)
|
||||
|
||||
|
||||
class Ntfy:
|
||||
"""Ntfy client."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
session: ClientSession | None = None,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
token: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize Ntfy client.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
url : str
|
||||
The base URL for the Ntfy service.
|
||||
session : ClientSession, optional
|
||||
An existing aiohttp ClientSession. If not provided, a new session will be created.
|
||||
"""
|
||||
self.url = URL(url)
|
||||
self._headers = None
|
||||
|
||||
if username is not None and password is not None:
|
||||
self._headers = {"Authorization": BasicAuth(username, password).encode()}
|
||||
elif token is not None:
|
||||
self._headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
if session is not None:
|
||||
self._session = session
|
||||
else:
|
||||
self._session = ClientSession(headers={"User-Agent": get_user_agent()})
|
||||
self._close_session = True
|
||||
|
||||
async def _request(self, method: str, url: URL, **kwargs: Any) -> str: # noqa: ANN401
|
||||
"""Handle API request.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
method : str
|
||||
HTTP method (e.g., 'GET', 'POST').
|
||||
url : URL
|
||||
The URL to send the request to.
|
||||
**kwargs : dict
|
||||
Additional arguments to pass to the request.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict[str, Any]
|
||||
The JSON response from the API.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyTimeoutError
|
||||
If a timeout occurs during the request.
|
||||
NtfyConnectionError
|
||||
If a client error occurs during the request.
|
||||
"""
|
||||
|
||||
if self._headers:
|
||||
kwargs.setdefault("headers", {}).update(self._headers)
|
||||
|
||||
try:
|
||||
async with self._session.request(method, url, **kwargs) as r:
|
||||
if r.status >= HTTPStatus.BAD_REQUEST:
|
||||
raise_http_error(**(await r.json()))
|
||||
return await r.text()
|
||||
except TimeoutError as e:
|
||||
raise NtfyTimeoutError from e
|
||||
except ClientError as e:
|
||||
raise NtfyConnectionError from e
|
||||
|
||||
async def publish(self, message: Message) -> Notification:
|
||||
"""Publish a message to an ntfy topic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : Message
|
||||
The message to be published, containing details such as topic, title, and content.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Notification
|
||||
A `Notification` object representing the response from the ntfy service.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyTimeoutError
|
||||
If a timeout occurs during the request.
|
||||
NtfyConnectionError
|
||||
If a client error occurs during the request.
|
||||
"""
|
||||
return Notification.from_json(
|
||||
await self._request("POST", self.url, json=message.to_dict())
|
||||
)
|
||||
|
||||
async def subscribe( # noqa: PLR0913
|
||||
self,
|
||||
topics: list[str],
|
||||
callback: Callable[[Notification], None],
|
||||
title: str | None = None,
|
||||
message: str | None = None,
|
||||
tags: list[str] | None = None,
|
||||
priority: list[int] | None = None,
|
||||
) -> None:
|
||||
"""Subscribe to one or more ntfy topics.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
topics : list[str]
|
||||
A list of topic names to subscribe to.
|
||||
callback : Callable[[Notification], None]
|
||||
A callback function that will be called when a new notification is received.
|
||||
The callback function should accept a single argument of type `Notification`.
|
||||
title : str, optional
|
||||
Filter: Only return messages that match this exact message string, defaults to None.
|
||||
message : str, optional
|
||||
Filter: Only return messages that match this exact title string, defaults to None.
|
||||
tags : list[str], optional
|
||||
Filter: Only return messages that match all listed tags, defaults to None
|
||||
priority : int, optional
|
||||
Filter: Only return messages that match any priority listed, defaults to None.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyTimeoutError
|
||||
If a timeout occurs during the subscription.
|
||||
NtfyConnectionError
|
||||
If a client error occurs during the subscription.
|
||||
|
||||
"""
|
||||
|
||||
await self.can_subscribe(topics)
|
||||
|
||||
url = (
|
||||
self.url.with_scheme("wss" if self.url.scheme == "https" else "ws")
|
||||
/ ",".join(topics)
|
||||
/ "ws"
|
||||
)
|
||||
params = {}
|
||||
if title is not None:
|
||||
params["title"] = title
|
||||
if message is not None:
|
||||
params["message"] = message
|
||||
if tags is not None:
|
||||
params["tags"] = ",".join(tags)
|
||||
if priority is not None:
|
||||
params["priority"] = ",".join(str(x) for x in priority)
|
||||
|
||||
try:
|
||||
async with self._session.ws_connect(
|
||||
url, params=params, headers=self._headers
|
||||
) as ws:
|
||||
async for msg in ws:
|
||||
if msg.type == WSMsgType.TEXT:
|
||||
callback(Notification.from_json(msg.data))
|
||||
elif msg.type in (
|
||||
WSMsgType.CLOSE,
|
||||
WSMsgType.CLOSING,
|
||||
WSMsgType.CLOSED,
|
||||
):
|
||||
break
|
||||
elif msg.type == WSMsgType.ERROR:
|
||||
continue
|
||||
except TimeoutError as e:
|
||||
raise NtfyTimeoutError from e
|
||||
except ClientError as e:
|
||||
raise NtfyConnectionError from e
|
||||
|
||||
async def can_subscribe(self, topics: list[str]) -> bool:
|
||||
"""Check if the client can subscribe to a topic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
topics : list of str
|
||||
A list of topic names to check subscription permissions for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the client can subscribe to the given topics.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyForbiddenAccessError
|
||||
If the client is not authorized to subscribe to the given topics.
|
||||
"""
|
||||
|
||||
await self._request("GET", self.url / ",".join(topics) / "auth")
|
||||
|
||||
return True
|
||||
|
||||
async def stats(self) -> Stats:
|
||||
"""Get message statistics.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Stats
|
||||
An instance of the `Stats` class containing message statistics.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return Stats.from_json(await self._request("GET", self.url / "v1/stats"))
|
||||
|
||||
async def account(self) -> Account:
|
||||
"""Get account information.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Account
|
||||
An instance of the `Account` class containing account information.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyUnauthorizedAuthenticationError
|
||||
If the client is not authorized to access the account information.
|
||||
"""
|
||||
return Account.from_json(await self._request("GET", self.url / "v1/account"))
|
||||
|
||||
async def generate_token(
|
||||
self,
|
||||
label: str | None = None,
|
||||
expires: datetime | None = None,
|
||||
) -> AccountTokenResponse:
|
||||
"""
|
||||
Generate a token for the account.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label : str, optional
|
||||
A label for the token, defaults to None.
|
||||
expires : datetime, optional
|
||||
The expiration date and time for the token. If not provided, the token will not expire.
|
||||
|
||||
Returns
|
||||
-------
|
||||
AccountTokenResponse
|
||||
An instance of `AccountTokenResponse` containing the generated token details.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyUnauthorizedAuthenticationError
|
||||
If the client is not authenticated.
|
||||
"""
|
||||
payload = {
|
||||
"label": label,
|
||||
"expires": int(expires.timestamp()) if expires else 0,
|
||||
}
|
||||
|
||||
return AccountTokenResponse.from_json(
|
||||
await self._request("POST", self.url / "v1/account/token", json=payload)
|
||||
)
|
||||
|
||||
async def reservation(self, topic: str, everyone: Everyone) -> bool:
|
||||
"""Reserve or change the reservation status of a topic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
topic : str
|
||||
The topic to reserve.
|
||||
everyone : str
|
||||
The reservation status to set for the topic.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if successfull.
|
||||
|
||||
"""
|
||||
|
||||
return Response.from_json(
|
||||
await self._request(
|
||||
"POST",
|
||||
self.url / "v1/account/reservation",
|
||||
json={"topic": topic, "everyone": everyone.value},
|
||||
)
|
||||
).success
|
||||
|
||||
async def delete_reservation(
|
||||
self, topic: str, *, delete_messages: bool = False
|
||||
) -> bool:
|
||||
"""Delete a topic reservation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
topic : str
|
||||
The name of the topic whose reservation is to be deleted.
|
||||
delete_messages : bool, optional
|
||||
If True, deletes all messages and attachments that are cached on the server
|
||||
otherwise they will become publicly available. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the reservation was successfully deleted, False otherwise.
|
||||
|
||||
Raises
|
||||
------
|
||||
NtfyUnauthorizedAuthenticationError
|
||||
If the client is not authenticated or the reservation does not exist.
|
||||
"""
|
||||
kwargs = {}
|
||||
|
||||
if delete_messages:
|
||||
kwargs["headers"] = {"X-Delete-Messages": "true"}
|
||||
|
||||
return Response.from_json(
|
||||
await self._request(
|
||||
"DELETE", self.url / "v1/account/reservation" / topic, **kwargs
|
||||
)
|
||||
).success
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close session.
|
||||
|
||||
Closes the aiohttp ClientSession if it is not already closed.
|
||||
"""
|
||||
if not self._session.closed:
|
||||
await self._session.close()
|
||||
|
||||
async def __aenter__(self) -> Self:
|
||||
"""Async enter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Self
|
||||
The Ntfy client instance.
|
||||
"""
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *exc_info: object) -> None:
|
||||
"""Async exit.
|
||||
|
||||
Closes the aiohttp ClientSession if it was created by this instance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*exc_info : object
|
||||
Exception information.
|
||||
"""
|
||||
if self._close_session:
|
||||
await self.close()
|
||||
@@ -0,0 +1,391 @@
|
||||
"""Type definitions for aiontfy."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import UTC, datetime
|
||||
from enum import IntEnum, StrEnum
|
||||
|
||||
from mashumaro import field_options
|
||||
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
||||
from yarl import URL
|
||||
|
||||
from .const import MAX_PRIORITY, MIN_PRIORITY
|
||||
|
||||
|
||||
class DeleteAfter(IntEnum):
|
||||
"""Delete after periods."""
|
||||
|
||||
NEVER = 0
|
||||
AFTER_THREE_HRS = 10800
|
||||
AFTER_ONE_DAY = 86400
|
||||
AFTER_ONE_WEEK = 604800
|
||||
AFTER_ONE_MONTH = 2592000
|
||||
|
||||
|
||||
class Priority(IntEnum):
|
||||
"""Message priority."""
|
||||
|
||||
MIN = 1
|
||||
LOW = 2
|
||||
DEFAULT = 3
|
||||
HIGH = 4
|
||||
MAX = 5
|
||||
|
||||
|
||||
class Sound(StrEnum):
|
||||
"""Notification sound."""
|
||||
|
||||
NO_SOUND = "none"
|
||||
DING = "ding"
|
||||
JUNTOS = "juntos"
|
||||
PRISTINE = "pristine"
|
||||
DADUM = "dadum"
|
||||
POP = "pop"
|
||||
POP_SWOOSH = "pop-swoosh"
|
||||
BEEP = "beep"
|
||||
|
||||
|
||||
class Everyone(StrEnum):
|
||||
"""Everyone access."""
|
||||
|
||||
DENY = "deny-all"
|
||||
READ = "read-only"
|
||||
WRITE = "write-only"
|
||||
READ_WRITE = "read-write"
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class HttpAction(DataClassORJSONMixin):
|
||||
"""An Http ntfy action.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
label : str
|
||||
Label of the action button in the notification.
|
||||
url : URL
|
||||
URL to which the HTTP request will be sent.
|
||||
method : str, optional
|
||||
HTTP method to use for request, default is POST.
|
||||
headers : dict[str, str] or None, optional
|
||||
HTTP headers to pass in request.
|
||||
body : str or None, optional
|
||||
HTTP body.
|
||||
clear : bool, optional
|
||||
Clear notification after HTTP request succeeds. If the request fails, the notification is not cleared.
|
||||
"""
|
||||
|
||||
action: str = field(default="http", init=False)
|
||||
label: str
|
||||
url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
|
||||
method: str = "POST"
|
||||
headers: dict[str, str] | None = None
|
||||
body: str | None = None
|
||||
clear: bool = False
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BroadcastAction(DataClassORJSONMixin):
|
||||
"""A broadcast ntfy action.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
label : str
|
||||
Label of the action button in the notification.
|
||||
intent : str or None, optional
|
||||
Android intent name, default is io.heckel.ntfy.USER_ACTION.
|
||||
extras : dict[str, str] or None, optional
|
||||
Android intent extras. Currently, only string extras are supported.
|
||||
clear : bool, optional
|
||||
Clear notification after action button is tapped.
|
||||
"""
|
||||
|
||||
action: str = field(default="broadcast", init=False)
|
||||
label: str
|
||||
intent: str | None = None
|
||||
extras: dict[str, str] | None = None
|
||||
clear: bool = False
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class ViewAction(DataClassORJSONMixin):
|
||||
"""A view ntfy action.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
label : str
|
||||
Label of the action button in the notification.
|
||||
url : URL
|
||||
URL to open when action is tapped.
|
||||
clear : bool, optional
|
||||
Clear notification after action button is tapped.
|
||||
"""
|
||||
|
||||
action: str = field(default="view", init=False)
|
||||
label: str
|
||||
url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
|
||||
clear: bool = False
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Message(DataClassORJSONMixin):
|
||||
"""A message to publish to ntfy.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
topic : str
|
||||
Target topic name.
|
||||
message : str or None, optional
|
||||
Message body; set to triggered if empty or not passed.
|
||||
title : str or None, optional
|
||||
Message title. Defaults to the topic short URL (ntfy.sh/mytopic) if not set.
|
||||
tags : list[str], optional
|
||||
List of tags that may or not map to emojis (https://docs.ntfy.sh/emojis/).
|
||||
priority : int or None, optional
|
||||
Message priority with 1=min, 3=default and 5=max
|
||||
actions : list[ViewAction or BroadcastAction or HttpAction], optional
|
||||
Custom user action buttons for notifications.
|
||||
click : URL or None, optional
|
||||
Website opened when notification is clicked.
|
||||
attach : URL or None, optional
|
||||
URL of an attachment.
|
||||
markdown : bool, optional
|
||||
Set to true if the message is Markdown-formatted.
|
||||
icon : URL or None, optional
|
||||
URL to use as notification icon.
|
||||
filename : str or None, optional
|
||||
File name of the attachment.
|
||||
delay : str or None, optional
|
||||
Timestamp or duration for delayed delivery.
|
||||
email : str or None, optional
|
||||
E-mail address for e-mail notifications.
|
||||
call : str or None, optional
|
||||
Phone number to use for voice call.
|
||||
|
||||
"""
|
||||
|
||||
topic: str
|
||||
message: str | None = None
|
||||
title: str | None = None
|
||||
tags: list[str] = field(default_factory=list)
|
||||
priority: int | None = None
|
||||
actions: list[ViewAction | BroadcastAction | HttpAction] = field(
|
||||
default_factory=list
|
||||
)
|
||||
click: URL | None = field(
|
||||
default=None, metadata=field_options(serialize=str, deserialize=URL)
|
||||
)
|
||||
attach: URL | None = field(
|
||||
default=None, metadata=field_options(serialize=str, deserialize=URL)
|
||||
)
|
||||
markdown: bool = False
|
||||
icon: URL | None = field(
|
||||
default=None, metadata=field_options(serialize=str, deserialize=URL)
|
||||
)
|
||||
filename: str | None = None
|
||||
delay: str | None = None
|
||||
email: str | None = None
|
||||
call: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Post-initialization processing to validate attributes.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the priority is not between the minimum and maximum allowed values.
|
||||
|
||||
"""
|
||||
|
||||
if self.priority is not None and (
|
||||
self.priority < MIN_PRIORITY or self.priority > MAX_PRIORITY
|
||||
):
|
||||
msg = f"Priority must be between {MIN_PRIORITY} and {MAX_PRIORITY}"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
class Event(StrEnum):
|
||||
"""Message type."""
|
||||
|
||||
OPEN = "open"
|
||||
KEEPALIVE = "keepalive"
|
||||
MESSAGE = "message"
|
||||
POLL_REQUEST = "poll_request"
|
||||
|
||||
|
||||
def timestamp(ts: int) -> datetime:
|
||||
"""Serialize timestamp to datetime."""
|
||||
return datetime.fromtimestamp(ts, tz=UTC)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Attachment(DataClassORJSONMixin):
|
||||
"""Details about an attachment."""
|
||||
|
||||
name: str
|
||||
url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
|
||||
type: str | None = None
|
||||
size: int | None = None
|
||||
expires: datetime | None = field(
|
||||
default=None, metadata=field_options(deserialize=timestamp)
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Notification(DataClassORJSONMixin):
|
||||
"""A notification received from a subscribed topic."""
|
||||
|
||||
id: str
|
||||
time: datetime = field(metadata=field_options(deserialize=timestamp))
|
||||
expires: datetime | None = field(
|
||||
default=None, metadata=field_options(deserialize=timestamp)
|
||||
)
|
||||
event: Event
|
||||
topic: str
|
||||
message: str | None = None
|
||||
title: str | None = None
|
||||
tags: list[str] = field(default_factory=list)
|
||||
priority: Priority | None = None
|
||||
click: URL | None = field(
|
||||
default=None, metadata=field_options(serialize=str, deserialize=URL)
|
||||
)
|
||||
icon: URL | None = field(
|
||||
default=None, metadata=field_options(serialize=str, deserialize=URL)
|
||||
)
|
||||
actions: list[ViewAction | BroadcastAction | HttpAction] = field(
|
||||
default_factory=list
|
||||
)
|
||||
attachment: Attachment | None = None
|
||||
content_type: str | None = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Stats(DataClassORJSONMixin):
|
||||
"""Stats response.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
messages : int
|
||||
The total number of messages.
|
||||
messages_rate : float
|
||||
Average number of messages per second.
|
||||
"""
|
||||
|
||||
messages: int
|
||||
messages_rate: float
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Subscription(DataClassORJSONMixin):
|
||||
"""Subscription information."""
|
||||
|
||||
base_url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
|
||||
topic: str
|
||||
display_name: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class NotificationPrefs(DataClassORJSONMixin):
|
||||
"""Notification preferences."""
|
||||
|
||||
sound: Sound | None = None
|
||||
min_priority: Priority | None = None
|
||||
delete_after: DeleteAfter | None = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AccountTokenResponse(DataClassORJSONMixin):
|
||||
"""Account token response."""
|
||||
|
||||
token: str
|
||||
label: str | None = None
|
||||
last_access: datetime = field(metadata=field_options(deserialize=timestamp))
|
||||
last_origin: str | None = None
|
||||
expires: datetime | None = field(
|
||||
default=None, metadata=field_options(deserialize=timestamp)
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AccountTier(DataClassORJSONMixin):
|
||||
"""Account tear information."""
|
||||
|
||||
code: str
|
||||
name: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AccountLimits(DataClassORJSONMixin):
|
||||
"""Account limits information."""
|
||||
|
||||
basis: str | None = None
|
||||
messages: int
|
||||
messages_expiry_duration: int
|
||||
emails: int
|
||||
calls: int
|
||||
reservations: int
|
||||
attachment_total_size: int
|
||||
attachment_file_size: int
|
||||
attachment_expiry_duration: int
|
||||
attachment_bandwidth: int
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AccountStats(DataClassORJSONMixin):
|
||||
"""Account stats."""
|
||||
|
||||
messages: int
|
||||
messages_remaining: int
|
||||
emails: int
|
||||
emails_remaining: int
|
||||
calls: int
|
||||
calls_remaining: int
|
||||
reservations: int
|
||||
reservations_remaining: int
|
||||
attachment_total_size: int
|
||||
attachment_total_size_remaining: int
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Reservation(DataClassORJSONMixin):
|
||||
"""Topic reservation settings."""
|
||||
|
||||
topic: str
|
||||
everyone: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AccountBilling(DataClassORJSONMixin):
|
||||
"""Acount billing information."""
|
||||
|
||||
customer: bool
|
||||
subscription: bool
|
||||
status: str | None = None
|
||||
interval: str | None = None
|
||||
paid_until: datetime = field(metadata=field_options(deserialize=timestamp))
|
||||
cancel_at: datetime | None = field(
|
||||
default=None, metadata=field_options(deserialize=timestamp)
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Account(DataClassORJSONMixin):
|
||||
"""Account response."""
|
||||
|
||||
username: str
|
||||
role: str | None = None
|
||||
sync_topic: str | None = None
|
||||
language: str | None = None
|
||||
notification: NotificationPrefs | None = None
|
||||
subscriptions: list[Subscription] = field(default_factory=list)
|
||||
reservations: list[Reservation] = field(default_factory=list)
|
||||
tokens: list[AccountTokenResponse] = field(default_factory=list)
|
||||
tier: AccountTier | None = None
|
||||
limits: AccountLimits | None = None
|
||||
stats: AccountStats
|
||||
billing: AccountBilling | None = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Response(DataClassORJSONMixin):
|
||||
"""Success response."""
|
||||
|
||||
success: bool
|
||||
Reference in New Issue
Block a user