ddns 4.1.0b1__py2.py3-none-any.whl → 4.1.0b2__py2.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,129 @@
1
+ # coding=utf-8
2
+ """
3
+ AliESA API
4
+ 阿里云边缘安全加速(ESA) DNS 解析操作库
5
+ @author: NewFuture, GitHub Copilot
6
+ """
7
+
8
+ from .alidns import AliBaseProvider
9
+ from ._base import join_domain, TYPE_JSON
10
+ from time import strftime
11
+
12
+
13
+ class AliesaProvider(AliBaseProvider):
14
+ """阿里云边缘安全加速(ESA) DNS Provider"""
15
+
16
+ endpoint = "https://esa.cn-hangzhou.aliyuncs.com"
17
+ api_version = "2024-09-10" # ESA API版本
18
+ content_type = TYPE_JSON
19
+ remark = "Managed by DDNS %s" % strftime("%Y-%m-%d %H:%M:%S")
20
+
21
+ def _query_zone_id(self, domain):
22
+ # type: (str) -> str | None
23
+ """
24
+ 查询站点ID
25
+ https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-listsites
26
+ """
27
+ res = self._request(method="GET", action="ListSites", SiteName=domain, PageSize=500)
28
+ sites = res.get("Sites", [])
29
+
30
+ for site in sites:
31
+ if site.get("SiteName") == domain:
32
+ site_id = site.get("SiteId")
33
+ self.logger.debug("Found site ID %s for domain %s", site_id, domain)
34
+ return site_id
35
+
36
+ self.logger.error("Site not found for domain: %s", domain)
37
+ return None
38
+
39
+ def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
40
+ # type: (str, str, str, str, str | None, dict) -> dict | None
41
+ """
42
+ 查询DNS记录
43
+ https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-listrecords
44
+ """
45
+ full_domain = join_domain(subdomain, main_domain)
46
+ res = self._request(
47
+ method="GET",
48
+ action="ListRecords",
49
+ SiteId=int(zone_id),
50
+ RecordName=full_domain,
51
+ Type=self._get_type(record_type),
52
+ RecordMatchType="exact", # 精确匹配
53
+ PageSize=100,
54
+ )
55
+
56
+ records = res.get("Records", [])
57
+ if len(records) == 0:
58
+ self.logger.warning("No records found for [%s] with %s <%s>", zone_id, full_domain, record_type)
59
+ return None
60
+
61
+ # 返回第一个匹配的记录
62
+ record = records[0]
63
+ self.logger.debug("Found record: %s", record)
64
+ return record
65
+
66
+ def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
67
+ # type: (str, str, str, str, str, int | str | None, str | None, dict) -> bool
68
+ """
69
+ 创建DNS记录
70
+ https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-createrecord
71
+ """
72
+ full_domain = join_domain(subdomain, main_domain)
73
+ extra["Comment"] = extra.get("Comment", self.remark)
74
+ extra["BizName"] = extra.get("BizName", "web")
75
+ extra["Proxied"] = extra.get("Proxied", True)
76
+ data = self._request(
77
+ method="POST",
78
+ action="CreateRecord",
79
+ SiteId=int(zone_id),
80
+ RecordName=full_domain,
81
+ Type=self._get_type(record_type),
82
+ Data={"Value": value},
83
+ Ttl=ttl or 1,
84
+ **extra
85
+ )
86
+
87
+ if data and data.get("RecordId"):
88
+ self.logger.info("Record created: %s", data)
89
+ return True
90
+
91
+ self.logger.error("Failed to create record: %s", data)
92
+ return False
93
+
94
+ def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
95
+ # type: (str, dict, str, str, int | str | None, str | None, dict) -> bool
96
+ """
97
+ 更新DNS记录
98
+ https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-updaterecord
99
+ """
100
+ # 检查是否需要更新
101
+ if (
102
+ old_record.get("Data", {}).get("Value") == value
103
+ and old_record.get("RecordType") == self._get_type(record_type)
104
+ and (not ttl or old_record.get("Ttl") == ttl)
105
+ ):
106
+ self.logger.warning("No changes detected, skipping update for record: %s", old_record.get("RecordName"))
107
+ return True
108
+
109
+ extra["Comment"] = extra.get("Comment", self.remark)
110
+ extra["Proxied"] = extra.get("Proxied", old_record.get("Proxied"))
111
+ data = self._request(
112
+ method="POST",
113
+ action="UpdateRecord",
114
+ RecordId=old_record.get("RecordId"),
115
+ Data={"Value": value},
116
+ Ttl=ttl,
117
+ **extra
118
+ )
119
+
120
+ if data and data.get("RecordId"):
121
+ self.logger.info("Record updated: %s", data)
122
+ return True
123
+
124
+ self.logger.error("Failed to update record: %s", data)
125
+ return False
126
+
127
+ def _get_type(self, record_type):
128
+ # type: (str) -> str
129
+ return "A/AAAA" if record_type in ("A", "AAAA") else record_type
ddns/provider/callback.py CHANGED
@@ -16,7 +16,7 @@ class CallbackProvider(SimpleProvider):
16
16
  Generic custom callback provider, supports GET/POST arbitrary API.
17
17
  """
18
18
 
19
- API = "" # CallbackProvider uses auth_id as URL, no fixed API endpoint
19
+ endpoint = "" # CallbackProvider uses id as URL, no fixed API endpoint
20
20
  content_type = TYPE_JSON
21
21
  decode_response = False # Callback response is not JSON, it's a custom response
22
22
 
@@ -26,9 +26,8 @@ class CallbackProvider(SimpleProvider):
26
26
  Send custom callback request, support GET/POST
27
27
  """
28
28
  self.logger.info("%s => %s(%s)", domain, value, record_type)
29
- url = self.auth_id # 直接用 auth_id 作为 url
30
- token = self.auth_token # auth_token 作为 POST 参数
31
- headers = {"User-Agent": "DDNS/{0} (ddns@newfuture.cc)".format(self.version)}
29
+ url = self.id # 直接用 id 作为 url
30
+ token = self.token # token 作为 POST 参数
32
31
  extra.update(
33
32
  {
34
33
  "__DOMAIN__": domain,
@@ -51,7 +50,7 @@ class CallbackProvider(SimpleProvider):
51
50
  params[k] = self._replace_vars(v, extra)
52
51
 
53
52
  try:
54
- res = self._http(method, url, body=params, headers=headers)
53
+ res = self._http(method, url, body=params)
55
54
  if res is not None:
56
55
  self.logger.info("Callback result: %s", res)
57
56
  return True
@@ -72,8 +71,8 @@ class CallbackProvider(SimpleProvider):
72
71
  return string
73
72
 
74
73
  def _validate(self):
75
- # CallbackProvider uses auth_id as URL, not as regular ID
76
- if not self.auth_id or "://" not in self.auth_id:
77
- self.logger.critical("callback ID 参数[%s] 必须是有效的URL", self.auth_id)
78
- raise ValueError("id must be configured with URL")
79
- # CallbackProvider doesn't need auth_token validation (it can be empty)
74
+ # CallbackProvider uses id as URL, not as regular ID
75
+ if self.endpoint or (not self.id or "://" not in self.id):
76
+ # 如果 endpoint 已经设置,或者 id 不是有效的 URL,则抛出异常
77
+ self.logger.critical("endpoint [%s] or id [%s] 必须是有效的URL", self.endpoint, self.id)
78
+ raise ValueError("endpoint or id must be configured with URL")
@@ -8,26 +8,26 @@ from ._base import BaseProvider, TYPE_JSON, join_domain
8
8
 
9
9
 
10
10
  class CloudflareProvider(BaseProvider):
11
- API = "https://api.cloudflare.com"
11
+ endpoint = "https://api.cloudflare.com"
12
12
  content_type = TYPE_JSON
13
13
 
14
14
  def _validate(self):
15
- if not self.auth_token:
15
+ if not self.token:
16
16
  raise ValueError("token must be configured")
17
- if self.auth_id:
17
+ if self.id:
18
18
  # must be email for Cloudflare API v4
19
- if "@" not in self.auth_id:
19
+ if "@" not in self.id:
20
20
  self.logger.critical("ID 必须为空或有效的邮箱地址")
21
21
  raise ValueError("ID must be a valid email or Empty for Cloudflare API v4")
22
22
 
23
23
  def _request(self, method, action, **params):
24
24
  """发送请求数据"""
25
25
  headers = {}
26
- if self.auth_id:
27
- headers["X-Auth-Email"] = self.auth_id
28
- headers["X-Auth-Key"] = self.auth_token
26
+ if self.id:
27
+ headers["X-Auth-Email"] = self.id
28
+ headers["X-Auth-Key"] = self.token
29
29
  else:
30
- headers["Authorization"] = "Bearer " + self.auth_token
30
+ headers["Authorization"] = "Bearer " + self.token
31
31
 
32
32
  params = {k: v for k, v in params.items() if v is not None} # 过滤掉None参数
33
33
  data = self._http(method, "/client/v4/zones" + action, headers=headers, params=params)
ddns/provider/dnscom.py CHANGED
@@ -5,7 +5,7 @@ www.51dns.com (原dns.com)
5
5
  @author: Bigjin<i@bigjin.com>, NewFuture
6
6
  """
7
7
 
8
- from ._base import BaseProvider, TYPE_FORM
8
+ from ._base import BaseProvider, TYPE_FORM, encode_params
9
9
  from hashlib import md5
10
10
  from time import time
11
11
 
@@ -16,7 +16,7 @@ class DnscomProvider(BaseProvider):
16
16
  https://www.51dns.com/document/api/index.html
17
17
  """
18
18
 
19
- API = "https://www.51dns.com"
19
+ endpoint = "https://www.51dns.com"
20
20
  content_type = TYPE_FORM
21
21
 
22
22
  def _validate(self):
@@ -31,12 +31,12 @@ class DnscomProvider(BaseProvider):
31
31
  params = {k: v for k, v in params.items() if v is not None}
32
32
  params.update(
33
33
  {
34
- "apiKey": self.auth_id,
34
+ "apiKey": self.id,
35
35
  "timestamp": time(), # 时间戳
36
36
  }
37
37
  )
38
- query = self._encode(sorted(params.items()))
39
- sign = md5((query + self.auth_token).encode("utf-8")).hexdigest()
38
+ query = encode_params(params)
39
+ sign = md5((query + self.token).encode("utf-8")).hexdigest()
40
40
  params["hash"] = sign
41
41
  return params
42
42
 
ddns/provider/dnspod.py CHANGED
@@ -14,7 +14,7 @@ class DnspodProvider(BaseProvider):
14
14
  DNSPOD 接口解析操作库
15
15
  """
16
16
 
17
- API = "https://dnsapi.cn"
17
+ endpoint = "https://dnsapi.cn"
18
18
  content_type = TYPE_FORM
19
19
 
20
20
  DefaultLine = "默认"
@@ -36,9 +36,8 @@ class DnspodProvider(BaseProvider):
36
36
  if extra:
37
37
  params.update(extra)
38
38
  params = {k: v for k, v in params.items() if v is not None}
39
- params.update({"login_token": "{0},{1}".format(self.auth_id, self.auth_token), "format": "json"})
40
- headers = {"User-Agent": "DDNS/{0} (ddns@newfuture.cc)".format(self.version)}
41
- data = self._http("POST", "/" + action, headers=headers, body=params)
39
+ params.update({"login_token": "{0},{1}".format(self.id, self.token), "format": "json"})
40
+ data = self._http("POST", "/" + action, body=params)
42
41
  if data and data.get("status", {}).get("code") == "1": # 请求成功
43
42
  return data
44
43
  else: # 请求失败
@@ -14,5 +14,5 @@ class DnspodComProvider(DnspodProvider):
14
14
  This class extends the DnspodProvider to use the global DNSPOD API.
15
15
  """
16
16
 
17
- API = "https://api.dnspod.com"
17
+ endpoint = "https://api.dnspod.com"
18
18
  DefaultLine = "default"
@@ -0,0 +1,82 @@
1
+ # coding=utf-8
2
+ """
3
+ Tencent Cloud EdgeOne API
4
+ 腾讯云 EdgeOne (边缘安全速平台) API
5
+ API Documentation: https://cloud.tencent.com/document/api/1552/80731
6
+ @author: NewFuture
7
+ """
8
+ from ddns.provider._base import join_domain
9
+ from .tencentcloud import TencentCloudProvider
10
+
11
+
12
+ class EdgeOneProvider(TencentCloudProvider):
13
+ """
14
+ 腾讯云 EdgeOne API 提供商
15
+ Tencent Cloud EdgeOne API Provider
16
+ """
17
+
18
+ endpoint = "https://teo.tencentcloudapi.com"
19
+ # 腾讯云 EdgeOne API 配置
20
+ service = "teo"
21
+ version_date = "2022-09-01"
22
+
23
+ def _query_zone_id(self, domain):
24
+ # type: (str) -> str | None
25
+ """查询域名的加速域名信息获取 ZoneId https://cloud.tencent.com/document/api/1552/80713"""
26
+ # 首先尝试直接查找域名
27
+ filters = [{"Name": "zone-name", "Values": [domain], "Fuzzy": False}] # type: Any
28
+ response = self._request("DescribeZones", Filters=filters)
29
+
30
+ if response and "Zones" in response:
31
+ for zone in response.get("Zones", []):
32
+ if zone.get("ZoneName") == domain:
33
+ zone_id = zone.get("ZoneId")
34
+ if zone_id:
35
+ self.logger.debug("Found acceleration domain %s with Zone ID: %s", domain, zone_id)
36
+ return zone_id
37
+
38
+ self.logger.debug("Acceleration domain not found for: %s", domain)
39
+ return None
40
+
41
+ def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
42
+ # type: (str, str, str, str, str | None, dict) -> dict | None
43
+ """查询加速域名信息 https://cloud.tencent.com/document/api/1552/86336"""
44
+ domain = join_domain(subdomain, main_domain)
45
+ filters = [{"Name": "domain-name", "Values": [domain], "Fuzzy": False}] # type: Any
46
+ response = self._request("DescribeAccelerationDomains", ZoneId=zone_id, Filters=filters)
47
+
48
+ if response and "AccelerationDomains" in response:
49
+ for domain_info in response.get("AccelerationDomains", []):
50
+ if domain_info.get("DomainName") == domain:
51
+ self.logger.debug("Found acceleration domain: %s", domain_info)
52
+ return domain_info
53
+
54
+ self.logger.warning("No acceleration domain found for: %s, response: %s", domain, response)
55
+ return None
56
+
57
+ def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
58
+ # type: (str, str, str, str, str, int, str | None, dict) -> bool
59
+ """创建新的加速域名记录 https://cloud.tencent.com/document/api/1552/86338"""
60
+ domain = join_domain(subdomain, main_domain)
61
+ origin = {"OriginType": "IP_DOMAIN", "Origin": value} # type: Any
62
+ res = self._request("CreateAccelerationDomain", ZoneId=zone_id, DomainName=domain, OriginInfo=origin, **extra)
63
+ if res:
64
+ self.logger.info("Acceleration domain created (%s)", res.get("RequestId"))
65
+ return True
66
+
67
+ self.logger.error("Failed to create acceleration domain, response: %s", res)
68
+ return False
69
+
70
+ def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
71
+ """更新加速域名的源站 IP 地址 https://cloud.tencent.com/document/api/1552/86335"""
72
+ domain = old_record.get("DomainName")
73
+ # 构建源站信息
74
+ backup = old_record.get("OriginDetail", {}).get("BackupOrigin", "")
75
+ origin = {"OriginType": "IP_DOMAIN", "Origin": value, "BackupOrigin": backup} # type: Any
76
+ response = self._request("ModifyAccelerationDomain", ZoneId=zone_id, DomainName=domain, OriginInfo=origin)
77
+
78
+ if response:
79
+ self.logger.info("Acceleration domain updated (%s)", response.get("RequestId"))
80
+ return True
81
+ self.logger.error("Failed to update acceleration domain origin, response: %s", response)
82
+ return False
ddns/provider/he.py CHANGED
@@ -8,7 +8,7 @@ from ._base import SimpleProvider, TYPE_FORM
8
8
 
9
9
 
10
10
  class HeProvider(SimpleProvider):
11
- API = "https://dyn.dns.he.net"
11
+ endpoint = "https://dyn.dns.he.net"
12
12
  content_type = TYPE_FORM
13
13
  accept = None # he.net does not require a specific Accept header
14
14
  decode_response = False # he.net response is plain text, not JSON
@@ -18,9 +18,9 @@ class HeProvider(SimpleProvider):
18
18
  "HE.net 缺少充分的真实环境测试,请及时在 GitHub Issues 中反馈: %s",
19
19
  "https://github.com/NewFuture/DDNS/issues",
20
20
  )
21
- if self.auth_id:
21
+ if self.id:
22
22
  raise ValueError("Hurricane Electric (he.net) does not use `id`, use `token(password)` only.")
23
- if not self.auth_token:
23
+ if not self.token:
24
24
  raise ValueError("Hurricane Electric (he.net) requires `token(password)`.")
25
25
 
26
26
  def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
@@ -32,7 +32,7 @@ class HeProvider(SimpleProvider):
32
32
  params = {
33
33
  "hostname": domain, # he.net requires full domain name
34
34
  "myip": value, # IP address to update
35
- "password": self.auth_token, # Use auth_token as password
35
+ "password": self.token, # Use token as password
36
36
  }
37
37
  try:
38
38
  res = self._http("POST", "/nic/update", body=params)
@@ -5,13 +5,13 @@ HuaweiDNS API
5
5
  @author: NewFuture
6
6
  """
7
7
 
8
- from ._base import BaseProvider, TYPE_JSON, hmac_sha256_authorization, sha256_hash, join_domain
9
- from json import dumps as jsonencode
8
+ from ._base import BaseProvider, TYPE_JSON, join_domain, encode_params
9
+ from ._signature import hmac_sha256_authorization, sha256_hash
10
10
  from time import strftime, gmtime
11
11
 
12
12
 
13
13
  class HuaweiDNSProvider(BaseProvider):
14
- API = "https://dns.myhuaweicloud.com"
14
+ endpoint = "https://dns.myhuaweicloud.com"
15
15
  content_type = TYPE_JSON
16
16
  algorithm = "SDK-HMAC-SHA256"
17
17
 
@@ -32,16 +32,16 @@ class HuaweiDNSProvider(BaseProvider):
32
32
  # type: (str, str, **Any) -> dict
33
33
  params = {k: v for k, v in params.items() if v is not None}
34
34
  if method.upper() == "GET" or method.upper() == "DELETE":
35
- query = self._encode(sorted(params.items()))
35
+ query = encode_params(params)
36
36
  body = ""
37
37
  else:
38
38
  query = ""
39
- body = jsonencode(params)
39
+ body = self._encode_body(params)
40
40
 
41
41
  now = strftime("%Y%m%dT%H%M%SZ", gmtime())
42
42
  headers = {
43
43
  "content-type": self.content_type,
44
- "host": self.API.split("://", 1)[1].strip("/"),
44
+ "host": self.endpoint.split("://", 1)[1].strip("/"),
45
45
  "X-Sdk-Date": now,
46
46
  }
47
47
 
@@ -51,10 +51,10 @@ class HuaweiDNSProvider(BaseProvider):
51
51
  sign_path = path if path.endswith("/") else path + "/"
52
52
  authorization_format = "%s Access=%s, SignedHeaders={SignedHeaders}, Signature={Signature}" % (
53
53
  self.algorithm,
54
- self.auth_id,
54
+ self.id,
55
55
  )
56
56
  authorization = hmac_sha256_authorization(
57
- secret_key=self.auth_token,
57
+ secret_key=self.token,
58
58
  method=method,
59
59
  path=sign_path,
60
60
  query=query,
@@ -0,0 +1,159 @@
1
+ # coding=utf-8
2
+ """
3
+ NameSilo API Provider
4
+ DNS provider implementation for NameSilo domain registrar
5
+ @doc: https://www.namesilo.com/api-reference
6
+ @author: NewFuture & Copilot
7
+ """
8
+
9
+ from ._base import BaseProvider, TYPE_JSON
10
+
11
+
12
+ class NamesiloProvider(BaseProvider):
13
+ """
14
+ NameSilo DNS API Provider
15
+
16
+ Supports DNS record management through NameSilo's API including:
17
+ - Domain information retrieval
18
+ - DNS record listing
19
+ - DNS record creation
20
+ - DNS record updating
21
+ """
22
+
23
+ endpoint = "https://www.namesilo.com"
24
+ content_type = TYPE_JSON
25
+
26
+ def _validate(self):
27
+ """Validate authentication credentials"""
28
+ # NameSilo only requires API key (token), not ID
29
+ if not self.token:
30
+ raise ValueError("API key (token) must be configured for NameSilo")
31
+ if not self.endpoint:
32
+ raise ValueError("API endpoint must be defined in {}".format(self.__class__.__name__))
33
+
34
+ # Warn if ID is configured since NameSilo doesn't need it
35
+ if self.id:
36
+ self.logger.warning("NameSilo does not require 'id' configuration - only API key (token) is needed")
37
+
38
+ # Show pending verification warning
39
+ self.logger.warning("NameSilo provider implementation is pending verification - please test thoroughly")
40
+
41
+ def _request(self, operation, **params):
42
+ # type: (str, **(str | int | bytes | bool | None)) -> dict|None
43
+ """
44
+ Send request to NameSilo API
45
+
46
+ Args:
47
+ operation (str): API operation name
48
+ params: API parameters
49
+
50
+ Returns:
51
+ dict: API response data
52
+ """
53
+ # Filter out None parameters
54
+ params = {k: v for k, v in params.items() if v is not None}
55
+
56
+ # Add required authentication and format parameters
57
+ params.update({"version": "1", "type": "json", "key": self.token})
58
+
59
+ # Make API request
60
+ response = self._http("GET", "/api/" + operation, queries=params)
61
+
62
+ # Parse response
63
+ if response and isinstance(response, dict):
64
+ reply = response.get("reply", {})
65
+
66
+ # Check for successful response
67
+ if reply.get("code") == "300": # NameSilo success code
68
+ return reply
69
+ else:
70
+ # Log error details
71
+ error_msg = reply.get("detail", "Unknown error")
72
+ self.logger.warning("NameSilo API error [%s]: %s", reply.get("code", "unknown"), error_msg)
73
+
74
+ return None
75
+
76
+ def _query_zone_id(self, domain):
77
+ # type: (str) -> str | None
78
+ """
79
+ Query domain information to get domain as zone identifier
80
+ @doc: https://www.namesilo.com/api-reference#domains/get-domain-info
81
+ """
82
+ response = self._request("getDomainInfo", domain=domain)
83
+
84
+ if response:
85
+ # Domain exists, return the domain name as zone_id
86
+ domain_info = response.get("domain", {})
87
+ if domain_info:
88
+ self.logger.debug("Domain found: %s", domain)
89
+ return domain
90
+
91
+ self.logger.warning("Domain not found or not accessible: %s", domain)
92
+ return None
93
+
94
+ def _query_record(self, zone_id, subdomain, main_domain, record_type, line, extra):
95
+ # type: (str, str, str, str, str | None, dict) -> dict | None
96
+ """
97
+ Query existing DNS record
98
+ @doc: https://www.namesilo.com/api-reference#dns/list-dns-records
99
+ """
100
+ response = self._request("dnsListRecords", domain=main_domain)
101
+
102
+ if response:
103
+ records = response.get("resource_record", [])
104
+
105
+ # Find matching record
106
+ for record in records:
107
+ if record.get("host") == subdomain and record.get("type") == record_type:
108
+ self.logger.debug("Found existing record: %s", record)
109
+ return record
110
+
111
+ self.logger.debug("No matching record found for %s.%s (%s)", subdomain, main_domain, record_type)
112
+ return None
113
+
114
+ def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl, line, extra):
115
+ # type: (str, str, str, str, str, int | str | None, str | None, dict) -> bool
116
+ """
117
+ Create new DNS record
118
+ @doc: https://www.namesilo.com/api-reference#dns/add-dns-record
119
+ """
120
+ response = self._request(
121
+ "dnsAddRecord", domain=main_domain, rrtype=record_type, rrhost=subdomain, rrvalue=value, rrttl=ttl
122
+ )
123
+
124
+ if response:
125
+ record_id = response.get("record_id")
126
+ self.logger.info("DNS record created successfully: %s", record_id)
127
+ return True
128
+ else:
129
+ self.logger.error("Failed to create DNS record")
130
+ return False
131
+
132
+ def _update_record(self, zone_id, old_record, value, record_type, ttl, line, extra):
133
+ # type: (str, dict, str, str, int | str | None, str | None, dict) -> bool
134
+ """
135
+ Update existing DNS record
136
+ @doc: https://www.namesilo.com/api-reference#dns/update-dns-record
137
+ """
138
+ record_id = old_record.get("record_id")
139
+ if not record_id:
140
+ self.logger.error("No record_id found in old_record: %s", old_record)
141
+ return False
142
+
143
+ # In NameSilo, zone_id is the main domain name
144
+ response = self._request(
145
+ "dnsUpdateRecord",
146
+ rrid=record_id,
147
+ domain=zone_id, # zone_id is main_domain in NameSilo
148
+ rrhost=old_record.get("host"), # host field contains subdomain
149
+ rrvalue=value,
150
+ rrtype=record_type,
151
+ rrttl=ttl or old_record.get("ttl"),
152
+ )
153
+
154
+ if response:
155
+ self.logger.info("DNS record updated successfully: %s", record_id)
156
+ return True
157
+ else:
158
+ self.logger.error("Failed to update DNS record")
159
+ return False