dnspod-tool 0.1.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.
@@ -0,0 +1,3 @@
1
+ """DNSPod command line and terminal UI tool."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from .main import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
dnspod_tool/api.py ADDED
@@ -0,0 +1,206 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from .config import Credential
7
+ from .errors import ApiError, ConfigError
8
+ from .models import Domain, Record
9
+
10
+ DEFAULT_RECORD_LINE = "\u9ed8\u8ba4"
11
+
12
+
13
+ class DnspodClient:
14
+ def __init__(self, credential: Credential, timeout: int = 10):
15
+ self.credential = credential
16
+ self.timeout = timeout
17
+ self._sdk_client: Any | None = None
18
+
19
+ def list_domains(self) -> list[Domain]:
20
+ items = self._paged("DescribeDomainList", ["domains", "DomainList"])
21
+ return [Domain.from_api(item) for item in items]
22
+
23
+ def list_records(self, domain: str) -> list[Record]:
24
+ items = self._paged("DescribeRecordList", ["records", "RecordList"], {"Domain": domain})
25
+ return [Record.from_api(item) for item in items]
26
+
27
+ def create_record(
28
+ self,
29
+ domain: str,
30
+ name: str,
31
+ record_type: str,
32
+ value: str,
33
+ line: str = DEFAULT_RECORD_LINE,
34
+ mx: int | None = None,
35
+ ttl: int | None = None,
36
+ ) -> dict[str, Any]:
37
+ params: dict[str, Any] = {
38
+ "Domain": domain,
39
+ "SubDomain": name,
40
+ "RecordType": record_type,
41
+ "RecordLine": line,
42
+ "Value": value,
43
+ }
44
+ if mx is not None:
45
+ params["MX"] = mx
46
+ if ttl is not None:
47
+ params["TTL"] = ttl
48
+ return self._call("CreateRecord", params)
49
+
50
+ def modify_record(
51
+ self,
52
+ domain: str,
53
+ record_id: str,
54
+ name: str,
55
+ record_type: str,
56
+ value: str,
57
+ line: str = DEFAULT_RECORD_LINE,
58
+ mx: int | None = None,
59
+ ttl: int | None = None,
60
+ ) -> dict[str, Any]:
61
+ params: dict[str, Any] = {
62
+ "Domain": domain,
63
+ "RecordId": record_id,
64
+ "SubDomain": name,
65
+ "RecordType": record_type,
66
+ "RecordLine": line,
67
+ "Value": value,
68
+ }
69
+ if mx is not None:
70
+ params["MX"] = mx
71
+ if ttl is not None:
72
+ params["TTL"] = ttl
73
+ return self._call("ModifyRecord", params)
74
+
75
+ def delete_record(self, domain: str, record_id: str) -> dict[str, Any]:
76
+ return self._call("DeleteRecord", {"Domain": domain, "RecordId": record_id})
77
+
78
+ def _paged(
79
+ self,
80
+ action: str,
81
+ item_keys: list[str],
82
+ params: dict[str, Any] | None = None,
83
+ limit: int = 100,
84
+ ) -> list[dict[str, Any]]:
85
+ items: list[dict[str, Any]] = []
86
+ offset = 0
87
+ base_params = params or {}
88
+ while True:
89
+ response = self._call(action, {**base_params, "Offset": offset, "Limit": limit})
90
+ batch = _first_list(response, item_keys)
91
+ items.extend(batch)
92
+ if len(batch) < limit:
93
+ break
94
+ offset += limit
95
+ return items
96
+
97
+ def _call(self, action: str, params: dict[str, Any]) -> dict[str, Any]:
98
+ clean_params = {key: value for key, value in params.items() if value is not None}
99
+ if "RecordId" in clean_params:
100
+ clean_params["RecordId"] = self._normalize_record_id(clean_params["RecordId"])
101
+
102
+ if self.credential.mode == "token":
103
+ return self._token_call(action, clean_params)
104
+ if self.credential.mode == "key":
105
+ return self._sdk_call(action, clean_params)
106
+ raise ConfigError(f"Unsupported credential mode: {self.credential.mode}")
107
+
108
+ def _token_call(self, action: str, params: dict[str, Any]) -> dict[str, Any]:
109
+ try:
110
+ import requests
111
+ except Exception as exc:
112
+ raise ConfigError("The requests package is required for token API mode.") from exc
113
+
114
+ api_map = {
115
+ "DescribeDomainList": "Domain.List",
116
+ "DescribeRecordList": "Record.List",
117
+ "CreateRecord": "Record.Create",
118
+ "DeleteRecord": "Record.Remove",
119
+ "ModifyRecord": "Record.Modify",
120
+ }
121
+ url = f"https://dnsapi.cn/{api_map.get(action, action)}"
122
+ payload = {"login_token": self.credential.login_token, "format": "json"}
123
+ payload.update(_to_token_params(params))
124
+ try:
125
+ response = requests.post(url, data=payload, timeout=self.timeout)
126
+ response.raise_for_status()
127
+ data = response.json()
128
+ except Exception as exc:
129
+ raise ApiError(f"DNSPod token API request failed: {exc}") from exc
130
+
131
+ status = data.get("status", {}) if isinstance(data, dict) else {}
132
+ if str(status.get("code")) != "1":
133
+ message = status.get("message") or data
134
+ raise ApiError(f"DNSPod API error: {message}")
135
+ return data
136
+
137
+ def _sdk_call(self, action: str, params: dict[str, Any]) -> dict[str, Any]:
138
+ try:
139
+ from tencentcloud.dnspod.v20210323 import models
140
+ except Exception as exc:
141
+ raise ConfigError("The Tencent Cloud SDK package is required for key mode.") from exc
142
+
143
+ method = getattr(self.sdk_client, action, None)
144
+ request_class = getattr(models, action + "Request", None)
145
+ if method is None or request_class is None:
146
+ raise ApiError(f"Unsupported SDK action: {action}")
147
+
148
+ request = request_class()
149
+ request.from_json_string(json.dumps(params))
150
+ try:
151
+ response = method(request)
152
+ return json.loads(response.to_json_string())
153
+ except Exception as exc:
154
+ raise ApiError(f"Tencent Cloud SDK request failed: {exc}") from exc
155
+
156
+ @property
157
+ def sdk_client(self) -> Any:
158
+ if self._sdk_client is not None:
159
+ return self._sdk_client
160
+ if not self.credential.secret_id or not self.credential.secret_key:
161
+ raise ConfigError("Tencent Cloud key credentials are incomplete.")
162
+ try:
163
+ from tencentcloud.common import credential
164
+ from tencentcloud.dnspod.v20210323 import dnspod_client
165
+ except Exception as exc:
166
+ raise ConfigError("The Tencent Cloud SDK package is required for key mode.") from exc
167
+ cred = credential.Credential(self.credential.secret_id, self.credential.secret_key)
168
+ self._sdk_client = dnspod_client.DnspodClient(cred, "")
169
+ return self._sdk_client
170
+
171
+ def _normalize_record_id(self, record_id: Any) -> Any:
172
+ if self.credential.mode == "key":
173
+ try:
174
+ return int(record_id)
175
+ except (TypeError, ValueError):
176
+ return record_id
177
+ return str(record_id)
178
+
179
+
180
+ def _to_token_params(params: dict[str, Any]) -> dict[str, Any]:
181
+ key_map = {
182
+ "Domain": "domain",
183
+ "SubDomain": "sub_domain",
184
+ "RecordType": "record_type",
185
+ "RecordLine": "record_line",
186
+ "Value": "value",
187
+ "RecordId": "record_id",
188
+ "MX": "mx",
189
+ "TTL": "ttl",
190
+ "Status": "status",
191
+ "Offset": "offset",
192
+ "Limit": "length",
193
+ }
194
+ return {
195
+ token_key: value
196
+ for source_key, token_key in key_map.items()
197
+ if (value := params.get(source_key)) is not None
198
+ }
199
+
200
+
201
+ def _first_list(data: dict[str, Any], keys: list[str]) -> list[dict[str, Any]]:
202
+ for key in keys:
203
+ value = data.get(key)
204
+ if isinstance(value, list):
205
+ return [item for item in value if isinstance(item, dict)]
206
+ return []