__main__.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. # -*- coding:utf-8 -*-
  2. """
  3. DDNS
  4. @author: NewFuture, rufengsuixing
  5. """
  6. from os import path, environ, name as os_name
  7. from io import TextIOWrapper
  8. from subprocess import check_output
  9. from tempfile import gettempdir
  10. from logging import basicConfig, getLogger, info, error, debug, warning, INFO
  11. import sys
  12. from .__init__ import __version__, __description__, __doc__, build_date
  13. from .util import ip
  14. from .util.cache import Cache
  15. from .util.config import init_config, get_config
  16. from .provider import get_provider_class, SimpleProvider # noqa: F401
  17. environ["DDNS_VERSION"] = __version__
  18. def is_false(value):
  19. """
  20. 判断值是否为 False
  21. 字符串 'false', 或者 False, 或者 'none';
  22. 0 不是 False
  23. """
  24. if hasattr(value, "strip"): # 字符串
  25. return value.strip().lower() in ["false", "none"]
  26. return value is False
  27. def get_ip(ip_type, index="default"):
  28. """
  29. get IP address
  30. """
  31. # CN: 捕获异常
  32. # EN: Catch exceptions
  33. value = None
  34. try:
  35. debug("get_ip(%s, %s)", ip_type, index)
  36. if is_false(index): # disabled
  37. return False
  38. elif isinstance(index, list): # 如果获取到的规则是列表,则依次判断列表中每一个规则,直到获取到IP
  39. for i in index:
  40. value = get_ip(ip_type, i)
  41. if value:
  42. break
  43. elif str(index).isdigit(): # 数字 local eth
  44. value = getattr(ip, "local_v" + ip_type)(index)
  45. elif index.startswith("cmd:"): # cmd
  46. value = str(check_output(index[4:]).strip().decode("utf-8"))
  47. elif index.startswith("shell:"): # shell
  48. value = str(check_output(index[6:], shell=True).strip().decode("utf-8"))
  49. elif index.startswith("url:"): # 自定义 url
  50. value = getattr(ip, "public_v" + ip_type)(index[4:])
  51. elif index.startswith("regex:"): # 正则 regex
  52. value = getattr(ip, "regex_v" + ip_type)(index[6:])
  53. else:
  54. value = getattr(ip, index + "_v" + ip_type)()
  55. except Exception as e:
  56. error("Failed to get %s address: %s", ip_type, e)
  57. return value
  58. def change_dns_record(dns, proxy_list, **kw):
  59. # type: (SimpleProvider, list, **(str)) -> bool
  60. for proxy in proxy_list:
  61. if not proxy or (proxy.upper() in ["DIRECT", "NONE"]):
  62. dns.set_proxy(None)
  63. else:
  64. dns.set_proxy(proxy)
  65. record_type, domain = kw["record_type"], kw["domain"]
  66. try:
  67. return dns.set_record(domain, kw["ip"], record_type=record_type, ttl=kw["ttl"])
  68. except Exception as e:
  69. error("Failed to update %s record for %s: %s", record_type, domain, e)
  70. return False
  71. def update_ip(ip_type, cache, dns, ttl, proxy_list):
  72. # type: (str, Cache | None, SimpleProvider, str, list[str]) -> bool | None
  73. """
  74. 更新IP
  75. """
  76. ipname = "ipv" + ip_type
  77. domains = get_config(ipname)
  78. if not domains:
  79. return None
  80. if not isinstance(domains, list):
  81. domains = domains.strip("; ").replace(",", ";").replace(" ", ";").split(";")
  82. index_rule = get_config("index" + ip_type, "default") # type: str # type: ignore
  83. address = get_ip(ip_type, index_rule)
  84. if not address:
  85. error("Fail to get %s address!", ipname)
  86. return False
  87. if cache and (address == cache.get(ipname)):
  88. info("%s address not changed, using cache.", ipname)
  89. return True
  90. record_type = "A" if ip_type == "4" else "AAAA"
  91. update_success = False
  92. for domain in domains:
  93. domain = domain.lower()
  94. if change_dns_record(dns, proxy_list, domain=domain, ip=address, record_type=record_type, ttl=ttl):
  95. warning("set %s[IPv%s]: %s successfully.", domain, ip_type, address)
  96. update_success = True
  97. if isinstance(cache, dict):
  98. cache[ipname] = update_success and address
  99. return update_success
  100. def main():
  101. """
  102. 更新
  103. """
  104. encode = sys.stdout.encoding
  105. if encode is not None and encode.lower() != "utf-8" and hasattr(sys.stdout, "buffer"):
  106. # 兼容windows 和部分ASCII编码的老旧系统
  107. sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
  108. sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
  109. init_config(__description__, __doc__, __version__, build_date)
  110. log_level = get_config("log.level", INFO) # type: int # type: ignore
  111. log_format = get_config("log.format") # type: str | None # type: ignore
  112. if log_format:
  113. # A custom log format is already set; no further action is required.
  114. pass
  115. elif log_level < INFO:
  116. # Override log format in debug mode to include filename and line number for detailed debugging
  117. log_format = "%(asctime)s %(levelname)s [%(name)s.%(funcName)s](%(filename)s:%(lineno)d): %(message)s"
  118. elif log_level > INFO:
  119. log_format = "%(asctime)s %(levelname)s: %(message)s"
  120. else:
  121. log_format = "%(asctime)s %(levelname)s [%(name)s]: %(message)s"
  122. basicConfig(
  123. level=log_level,
  124. format=log_format,
  125. datefmt=get_config("log.datefmt", "%Y-%m-%dT%H:%M:%S"), # type: ignore
  126. filename=get_config("log.file"), # type: ignore
  127. )
  128. logger = getLogger()
  129. logger.name = "ddns"
  130. debug("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
  131. # dns provider class
  132. dns_name = get_config("dns", "debug") # type: str # type: ignore
  133. provider_class = get_provider_class(dns_name)
  134. ssl_config = get_config("ssl", "auto") # type: str | bool # type: ignore
  135. dns = provider_class(get_config("id"), get_config("token"), logger=logger, verify_ssl=ssl_config) # type: ignore
  136. if get_config("config"):
  137. info("loaded Config from: %s", path.abspath(get_config("config"))) # type: ignore
  138. proxy = get_config("proxy") or "DIRECT"
  139. proxy_list = proxy if isinstance(proxy, list) else proxy.strip(";").replace(",", ";").split(";")
  140. cache_config = get_config("cache", True) # type: bool | str # type: ignore
  141. if cache_config is False:
  142. cache = None
  143. elif cache_config is True:
  144. cache = Cache(path.join(gettempdir(), "ddns.cache"), logger)
  145. else:
  146. cache = Cache(cache_config, logger)
  147. if cache is None:
  148. info("Cache is disabled!")
  149. elif get_config("config_modified_time", float("inf")) >= cache.time: # type: ignore
  150. info("Cache file is outdated.")
  151. cache.clear()
  152. elif len(cache) == 0:
  153. debug("Cache is empty.")
  154. else:
  155. debug("Cache loaded with %d entries.", len(cache))
  156. ttl = get_config("ttl") # type: str # type: ignore
  157. update_ip("4", cache, dns, ttl, proxy_list)
  158. update_ip("6", cache, dns, ttl, proxy_list)
  159. if __name__ == "__main__":
  160. main()