1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
|
"""Hotspot vouchers as part of a UniFi network."""
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import StrEnum
from typing import NotRequired, Self, TypedDict
from .api import ApiItem, ApiRequest
class VoucherStatus(StrEnum):
"""Voucher status."""
VALID_ONE = "VALID_ONE"
VALID_MULTI = "VALID_MULTI"
USED_MULTIPLE = "USED_MULTIPLE"
class TypedVoucher(TypedDict):
"""Voucher type definition."""
_id: str
site_id: str
note: NotRequired[str]
code: str
quota: int
duration: float
qos_overwrite: NotRequired[bool]
qos_usage_quota: NotRequired[str]
qos_rate_max_up: NotRequired[int]
qos_rate_max_down: NotRequired[int]
used: int
create_time: float
start_time: NotRequired[float]
end_time: NotRequired[float]
for_hotspot: NotRequired[bool]
admin_name: str
status: VoucherStatus
status_expires: float
@dataclass
class VoucherListRequest(ApiRequest):
"""Request object for voucher list."""
@classmethod
def create(cls) -> Self:
"""Create voucher list request."""
return cls(
method="get",
path="/stat/voucher",
)
@dataclass
class VoucherCreateRequest(ApiRequest):
"""Request object for voucher create."""
@classmethod
def create(
cls,
expire_number: int,
expire_unit: int = 1,
number: int = 1,
quota: int = 0,
usage_quota: int | None = None,
rate_max_up: int | None = None,
rate_max_down: int | None = None,
note: str | None = None,
) -> Self:
"""Create voucher create request.
:param expire_number: expiration of voucher per expire_unit
:param expire_unit: scale of expire_number, 1 = minute, 60 = hour, 3600 = day
:param number: number of vouchers
:param quota: number of using; 0 = unlimited
:param usage_quota: quantity of bytes allowed in MB
:param rate_max_up: up speed allowed in kbps
:param rate_max_down: down speed allowed in kbps
:param note: description
"""
data = {
"cmd": "create-voucher",
"n": number,
"quota": quota,
"expire_number": expire_number,
"expire_unit": expire_unit,
}
if usage_quota:
data["bytes"] = usage_quota
if rate_max_up:
data["up"] = rate_max_up
if rate_max_down:
data["down"] = rate_max_down
if note:
data["note"] = note
return cls(
method="post",
path="/cmd/hotspot",
data=data,
)
@dataclass
class VoucherDeleteRequest(ApiRequest):
"""Request object for voucher delete."""
@classmethod
def create(
cls,
obj_id: str,
) -> Self:
"""Create voucher delete request."""
data = {
"cmd": "delete-voucher",
"_id": obj_id,
}
return cls(
method="post",
path="/cmd/hotspot",
data=data,
)
class Voucher(ApiItem):
"""Represents a voucher."""
raw: TypedVoucher
@property
def id(self) -> str:
"""ID of voucher."""
return self.raw["_id"]
@property
def site_id(self) -> str:
"""Site ID."""
return self.raw["site_id"]
@property
def note(self) -> str:
"""Note given by creator to voucher."""
return self.raw.get("note") or ""
@property
def code(self) -> str:
"""Code of voucher in known format 00000-00000.
To enter the code on the captive portal, the hyphen must be placed after the fifth digit.
"""
c = self.raw.get("code", "")
# API returns the code without a hyphen. But this is necessary. Separate the API string after the fifth digit.
return f"{c[:5]}-{c[5:]}"
@property
def quota(self) -> int:
"""Allowed uses (0 = unlimited) of voucher."""
return self.raw["quota"]
@property
def duration(self) -> timedelta:
"""Expiration of voucher."""
return timedelta(minutes=self.raw["duration"])
@property
def qos_overwrite(self) -> bool:
"""Defaults for QoS overwritten by the use of this voucher."""
return self.raw.get("qos_overwrite", False)
@property
def qos_usage_quota(self) -> int:
"""Quantity of bytes (in MB) allowed when using this voucher."""
return int(self.raw.get("qos_usage_quota", 0))
@property
def qos_rate_max_up(self) -> int:
"""Up speed (in kbps) allowed when using this voucher."""
return self.raw.get("qos_rate_max_up", 0)
@property
def qos_rate_max_down(self) -> int:
"""Down speed (in kbps) allowed when using this voucher."""
return self.raw.get("qos_rate_max_down", 0)
@property
def used(self) -> int:
"""Number of uses of this voucher."""
return self.raw["used"]
@property
def create_time(self) -> datetime:
"""Create datetime of voucher."""
return datetime.fromtimestamp(self.raw["create_time"])
@property
def start_time(self) -> datetime | None:
"""Start datetime of first usage of voucher."""
if "start_time" in self.raw:
return datetime.fromtimestamp(self.raw["start_time"])
return None
@property
def end_time(self) -> datetime | None:
"""End datetime of latest usage of voucher."""
if "end_time" in self.raw:
return datetime.fromtimestamp(self.raw["end_time"])
return None
@property
def for_hotspot(self) -> bool:
"""For hotspot use."""
return self.raw.get("for_hotspot", False)
@property
def admin_name(self) -> str:
"""Creator name of voucher."""
return self.raw["admin_name"]
@property
def status(self) -> VoucherStatus:
"""Status of voucher."""
return self.raw["status"]
@property
def status_expires(self) -> timedelta | None:
"""Status expires in seconds."""
if (status_expiry := self.raw["status_expires"]) > 0:
return timedelta(seconds=status_expiry)
return None
|