__main__.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # -*- coding:utf-8 -*-
  2. """
  3. DDNS
  4. @author: NewFuture, rufengsuixing
  5. """
  6. import sys
  7. from io import TextIOWrapper
  8. from logging import getLogger
  9. from subprocess import check_output
  10. from . import ip
  11. from .__init__ import __description__, __version__, build_date
  12. from .cache import Cache
  13. from .config import Config, load_configs # noqa: F401
  14. from .provider import SimpleProvider, get_provider_class # noqa: F401
  15. logger = getLogger()
  16. def get_ip(ip_type, rules):
  17. """
  18. get IP address
  19. """
  20. if rules is False: # disabled
  21. return False
  22. for i in rules:
  23. try:
  24. logger.debug("get_ip:(%s, %s)", ip_type, i)
  25. if str(i).isdigit(): # 数字 local eth
  26. return getattr(ip, "local_v" + ip_type)(i)
  27. elif i.startswith("cmd:"): # cmd
  28. return str(check_output(i[4:]).strip().decode("utf-8"))
  29. elif i.startswith("shell:"): # shell
  30. return str(check_output(i[6:], shell=True).strip().decode("utf-8"))
  31. elif i.startswith("url:"): # 自定义 url
  32. return getattr(ip, "public_v" + ip_type)(i[4:])
  33. elif i.startswith("regex:"): # 正则 regex
  34. return getattr(ip, "regex_v" + ip_type)(i[6:])
  35. else:
  36. return getattr(ip, i + "_v" + ip_type)()
  37. except Exception as e:
  38. logger.error("Failed to get %s address: %s", ip_type, e)
  39. return None
  40. def update_ip(dns, cache, index_rule, domains, record_type, config):
  41. # type: (SimpleProvider, Cache | None, list[str]|bool, list[str], str, Config) -> bool | None
  42. """
  43. 更新IP并变更DNS记录
  44. """
  45. if not domains:
  46. return None
  47. ip_type = "4" if record_type == "A" else "6"
  48. address = get_ip(ip_type, index_rule)
  49. if not address:
  50. logger.error("Fail to get %s address!", ip_type)
  51. return False
  52. update_success = False
  53. for domain in domains:
  54. domain = domain.lower()
  55. cache_key = "{}:{}".format(domain, record_type)
  56. if cache and cache.get(cache_key) == address:
  57. logger.info("%s[%s] address not changed, using cache: %s", domain, record_type, address)
  58. update_success = True
  59. else:
  60. try:
  61. result = dns.set_record(
  62. domain, address, record_type=record_type, ttl=config.ttl, line=config.line, **config.extra
  63. )
  64. if result:
  65. logger.warning("set %s[IPv%s]: %s successfully.", domain, ip_type, address)
  66. update_success = True
  67. if isinstance(cache, dict):
  68. cache[cache_key] = address
  69. else:
  70. logger.error("Failed to update %s record for %s", record_type, domain)
  71. except Exception as e:
  72. logger.exception("Failed to update %s record for %s: %s", record_type, domain, e)
  73. return update_success
  74. def run(config):
  75. # type: (Config) -> bool
  76. """
  77. Run the DDNS update process
  78. """
  79. # 设置IP模块的SSL验证配置
  80. ip.ssl_verify = config.ssl
  81. # dns provider class
  82. provider_class = get_provider_class(config.dns)
  83. dns = provider_class(
  84. config.id, config.token, endpoint=config.endpoint, logger=logger, proxy=config.proxy, ssl=config.ssl
  85. )
  86. cache = Cache.new(config.cache, config.md5(), logger)
  87. return (
  88. update_ip(dns, cache, config.index4, config.ipv4, "A", config) is not False
  89. and update_ip(dns, cache, config.index6, config.ipv6, "AAAA", config) is not False
  90. )
  91. def main():
  92. stdout = sys.stdout # pythonw 模式无 stdout
  93. if stdout and stdout.encoding and stdout.encoding.lower() != "utf-8" and hasattr(stdout, "buffer"):
  94. # 兼容windows 和部分ASCII编码的老旧系统
  95. sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
  96. sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
  97. # Windows 下输出一个空行
  98. if stdout and sys.platform.startswith("win"):
  99. stdout.write("\r\n")
  100. logger.name = "ddns"
  101. # 使用多配置加载器,它会自动处理单个和多个配置
  102. configs = load_configs(__description__, __version__, build_date)
  103. if len(configs) == 1:
  104. # 单个配置,使用原有逻辑(向后兼容)
  105. config = configs[0]
  106. success = run(config)
  107. if not success:
  108. sys.exit(1)
  109. else:
  110. # 多个配置,使用新的批处理逻辑
  111. overall_success = True
  112. for i, config in enumerate(configs):
  113. # 如果log_level有值则设置setLevel
  114. if hasattr(config, "log_level") and config.log_level:
  115. logger.setLevel(config.log_level)
  116. logger.info("Running configuration %d/%d", i + 1, len(configs))
  117. # 记录当前provider
  118. logger.info("Using DNS provider: %s", config.dns)
  119. success = run(config)
  120. if not success:
  121. overall_success = False
  122. logger.error("Configuration %d failed", i + 1)
  123. else:
  124. logger.info("Configuration %d completed successfully", i + 1)
  125. if not overall_success:
  126. logger.error("Some configurations failed")
  127. sys.exit(1)
  128. else:
  129. logger.info("All configurations completed successfully")
  130. if __name__ == "__main__":
  131. main()