__main__.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # -*- coding:utf-8 -*-
  2. """
  3. DDNS
  4. @author: NewFuture, rufengsuixing
  5. """
  6. from io import TextIOWrapper
  7. from subprocess import check_output
  8. from logging import getLogger
  9. import sys
  10. from .__init__ import __version__, __description__, build_date
  11. from .config import load_configs, Config # noqa: F401
  12. from .provider import get_provider_class, SimpleProvider
  13. from . import ip
  14. from .cache import Cache
  15. logger = getLogger()
  16. # Set user agent for All Providers
  17. SimpleProvider.user_agent = SimpleProvider.user_agent.format(version=__version__)
  18. def get_ip(ip_type, rules):
  19. """
  20. get IP address
  21. """
  22. if rules is False: # disabled
  23. return False
  24. for i in rules:
  25. try:
  26. logger.debug("get_ip:(%s, %s)", ip_type, i)
  27. if str(i).isdigit(): # 数字 local eth
  28. return getattr(ip, "local_v" + ip_type)(i)
  29. elif i.startswith("cmd:"): # cmd
  30. return str(check_output(i[4:]).strip().decode("utf-8"))
  31. elif i.startswith("shell:"): # shell
  32. return str(check_output(i[6:], shell=True).strip().decode("utf-8"))
  33. elif i.startswith("url:"): # 自定义 url
  34. return getattr(ip, "public_v" + ip_type)(i[4:])
  35. elif i.startswith("regex:"): # 正则 regex
  36. return getattr(ip, "regex_v" + ip_type)(i[6:])
  37. else:
  38. return getattr(ip, i + "_v" + ip_type)()
  39. except Exception as e:
  40. logger.error("Failed to get %s address: %s", ip_type, e)
  41. return None
  42. def update_ip(dns, cache, index_rule, domains, record_type, config):
  43. # type: (SimpleProvider, Cache | None, list[str]|bool, list[str], str, Config) -> bool | None
  44. """
  45. 更新IP并变更DNS记录
  46. """
  47. if not domains:
  48. return None
  49. ip_type = "4" if record_type == "A" else "6"
  50. address = get_ip(ip_type, index_rule)
  51. if not address:
  52. logger.error("Fail to get %s address!", ip_type)
  53. return False
  54. update_success = False
  55. for domain in domains:
  56. domain = domain.lower()
  57. cache_key = "{}:{}".format(domain, record_type)
  58. if cache and cache.get(cache_key) == address:
  59. logger.info("%s[%s] address not changed, using cache: %s", domain, record_type, address)
  60. update_success = True
  61. else:
  62. try:
  63. result = dns.set_record(domain, address, record_type=record_type, ttl=config.ttl, line=config.line)
  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, verify_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. encode = sys.stdout.encoding
  93. if encode is not None and encode.lower() != "utf-8" and hasattr(sys.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. logger.name = "ddns"
  98. # 使用多配置加载器,它会自动处理单个和多个配置
  99. configs = load_configs(__description__, __version__, build_date)
  100. if len(configs) == 1:
  101. # 单个配置,使用原有逻辑(向后兼容)
  102. config = configs[0]
  103. success = run(config)
  104. if not success:
  105. sys.exit(1)
  106. else:
  107. # 多个配置,使用新的批处理逻辑
  108. overall_success = True
  109. for i, config in enumerate(configs):
  110. # 如果log_level有值则设置setLevel
  111. if hasattr(config, "log_level") and config.log_level:
  112. logger.setLevel(config.log_level)
  113. logger.info("Running configuration %d/%d", i + 1, len(configs))
  114. # 记录当前provider
  115. logger.info("Using DNS provider: %s", config.dns)
  116. success = run(config)
  117. if not success:
  118. overall_success = False
  119. logger.error("Configuration %d failed", i + 1)
  120. else:
  121. logger.info("Configuration %d completed successfully", i + 1)
  122. if not overall_success:
  123. logger.error("Some configurations failed")
  124. sys.exit(1)
  125. else:
  126. logger.info("All configurations completed successfully")
  127. if __name__ == "__main__":
  128. main()