| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- # -*- coding:utf-8 -*-
- """
- Configuration file loader for DDNS. supports both JSON and AST parsing.
- @author: NewFuture
- """
- from ast import literal_eval
- from io import open
- from json import loads as json_decode, dumps as json_encode
- from sys import stderr, stdout
- from ..util.comment import remove_comment
- def _process_multi_providers(config):
- # type: (dict) -> list[dict]
- """Process v4.1 providers format and return list of configs."""
- result = []
- # 提取全局配置(除providers之外的所有配置)
- global_config = _flatten_single_config(config, exclude_keys=["providers"])
- # 检查providers和dns字段不能同时使用
- if global_config.get("dns"):
- stderr.write("Error: 'providers' and 'dns' fields cannot be used simultaneously in config file!\n")
- raise ValueError("providers and dns fields conflict")
- # 为每个provider创建独立配置
- for provider_config in config["providers"]:
- # 验证provider必须有provider字段
- if not provider_config.get("provider"):
- stderr.write("Error: Each provider must have a 'provider' field!\n")
- raise ValueError("provider missing provider field")
- flat_config = global_config.copy() # 从全局配置开始
- provider_flat = _flatten_single_config(provider_config, exclude_keys=["provider"])
- flat_config["dns"] = provider_config.get("provider")
- flat_config.update(provider_flat)
- result.append(flat_config)
- return result
- def _flatten_single_config(config, exclude_keys=None):
- # type: (dict, list[str]|None) -> dict
- """Flatten a single config object with optional key exclusion."""
- if exclude_keys is None:
- exclude_keys = []
- flat_config = {}
- for k, v in config.items():
- if k in exclude_keys:
- continue
- if isinstance(v, dict):
- for subk, subv in v.items():
- flat_config["{}_{}".format(k, subk)] = subv
- else:
- flat_config[k] = v
- return flat_config
- def load_config(config_path):
- # type: (str) -> dict|list[dict]
- """
- 加载配置文件并返回配置字典或配置字典数组。
- 对于单个对象返回dict,对于数组返回list[dict]。
- 优先尝试JSON解析,失败后尝试AST解析。
- Args:
- config_path (str): 配置文件路径
- Returns:
- dict|list[dict]: 配置字典或配置字典数组
- Raises:
- Exception: 当配置文件加载失败时抛出异常
- """
- try:
- with open(config_path, "r", encoding="utf-8") as f:
- content = f.read()
- # 移除注释后尝试JSON解析
- content_without_comments = remove_comment(content)
- try:
- config = json_decode(content_without_comments)
- except (ValueError, SyntaxError) as json_error:
- # JSON解析失败,尝试AST解析
- try:
- config = literal_eval(content)
- stdout.write("Successfully loaded config file with AST parser: %s\n" % config_path)
- except (ValueError, SyntaxError) as ast_error:
- if config_path.endswith(".json"):
- stderr.write("JSON parsing failed for %s\n" % (config_path))
- raise json_error
- stderr.write(
- "Both JSON and AST parsing failed for %s\nJSON Error: %s\nAST Error: %s\n"
- % (config_path, json_error, ast_error)
- )
- raise ast_error
- except Exception as e:
- stderr.write("Failed to load config file `%s`: %s\n" % (config_path, e))
- raise
- # 处理配置格式:v4.1 providers格式或单个对象
- if "providers" in config and isinstance(config["providers"], list):
- return _process_multi_providers(config)
- else:
- return _flatten_single_config(config)
- def save_config(config_path, config):
- # type: (str, dict) -> bool
- """
- 保存配置到文件。
- Args:
- config_path (str): 配置文件路径
- config (dict): 配置字典
- Returns:
- bool: 保存成功返回True
- Raises:
- Exception: 保存失败时抛出异常
- """
- # 补全默认配置
- config = {
- "$schema": "https://ddns.newfuture.cc/schema/v4.1.json",
- "dns": config.get("dns", "debug"),
- "id": config.get("id", "YOUR ID or EMAIL for DNS Provider"),
- "token": config.get("token", "YOUR TOKEN or KEY for DNS Provider"),
- "ipv4": config.get("ipv4", ["ddns.newfuture.cc"]),
- "index4": config.get("index4", ["default"]),
- "ipv6": config.get("ipv6", []),
- "index6": config.get("index6", []),
- "ttl": config.get("ttl", 600),
- "line": config.get("line"),
- "proxy": config.get("proxy", []),
- "cache": config.get("cache", True),
- "ssl": config.get("ssl", "auto"),
- "log": {
- "file": config.get("log_file"),
- "level": config.get("log_level", "INFO"),
- "format": config.get("log_format"),
- "datefmt": config.get("log_datefmt"),
- },
- }
- try:
- with open(config_path, "w", encoding="utf-8") as f:
- content = json_encode(config, indent=2, ensure_ascii=False)
- # Python 2 兼容性:检查是否需要解码
- if hasattr(content, "decode"):
- content = content.decode("utf-8") # type: ignore
- f.write(content)
- return True
- except Exception:
- stderr.write("Cannot open config file to write: `%s`!\n" % config_path)
- raise
|