diff --git a/config-man.json b/config-man.json index 25d8126..aa446e0 100644 --- a/config-man.json +++ b/config-man.json @@ -6,6 +6,7 @@ }, "cdn": { "base_url": "https://128-ft-cdn-aws-tx.arksgame.com/ab", + "ru_base_url": "https://128-ft-cdn-ali-tx.arksgame.com/ru", "timeout": 10, "retry_count": 3, "cloudflare": { diff --git a/decryptConfig.py b/decryptConfig.py new file mode 100644 index 0000000..e4f46d7 --- /dev/null +++ b/decryptConfig.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Config Decryptor Tool + +读取加密后的配置文件(Base64 + DES-CBC),还原为原始 JSON 配置。 +""" + +import json +import os +import sys +from pathlib import Path + +from rich.console import Console +from rich.panel import Panel +from rich.syntax import Syntax + +# Add the src directory to the path so we can import the crypto module +sys.path.insert(0, str(Path(__file__).parent / "src")) + +from config_man.utils.crypto import decrypt + +# Initialize rich console +console = Console() + + +def load_encrypted_file(file_path): + """Load encrypted file content as a single string (strip whitespace).""" + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read().strip() + if not content: + console.print(f"[red]错误: 文件 {file_path} 为空[/red]") + return None + return content + except FileNotFoundError: + console.print(f"[red]错误: 文件 {file_path} 不存在[/red]") + return None + except Exception as e: + console.print(f"[red]错误: 无法读取文件 {file_path}: {str(e)}[/red]") + return None + + +def decrypt_to_config(encrypted_content): + """Decrypt and parse JSON into a Python object.""" + try: + plain = decrypt(encrypted_content) + return json.loads(plain) + except json.JSONDecodeError as e: + console.print(f"[red]错误: 解密后内容不是有效 JSON: {str(e)}[/red]") + return None + except Exception as e: + console.print(f"[red]错误: 解密失败: {str(e)}[/red]") + return None + + +def save_raw_config(config_data, file_path): + """Save decrypted config as formatted JSON.""" + try: + with open(file_path, "w", encoding="utf-8") as f: + json.dump(config_data, f, ensure_ascii=False, indent=2) + console.print(f"[green]✅ 解密后的配置文件已保存到: {file_path}[/green]") + return True + except Exception as e: + console.print(f"[red]错误: 无法保存文件 {file_path}: {str(e)}[/red]") + return False + + +def main(): + console.print(Panel("🔓 Config Decryptor Tool", expand=False, border_style="bold blue")) + + while True: + input_file = input("请输入要解密的配置文件路径 (如: encrypted_config.json): ").strip() + if input_file and os.path.exists(input_file): + break + console.print("[red]❌ 文件不存在,请重新输入[/red]") + + console.print(f"[blue]正在读取加密文件: {input_file}[/blue]") + encrypted_blob = load_encrypted_file(input_file) + if encrypted_blob is None: + return 1 + + console.print("[blue]正在解密…[/blue]") + config_data = decrypt_to_config(encrypted_blob) + if config_data is None: + return 1 + + console.print("[green]✅ 解密成功[/green]") + console.print(Panel("解密后的配置内容", title="📄 原始配置", border_style="cyan")) + console.print(Syntax(json.dumps(config_data, ensure_ascii=False, indent=2), "json", theme="monokai")) + + base = os.path.basename(input_file) + if base.startswith("encrypted_"): + default_output = "decrypted_" + base[len("encrypted_"):] + else: + default_output = "decrypted_" + base + + output_file = input(f"\n请输入输出文件路径 (默认: {default_output}): ").strip() + if not output_file: + output_file = default_output + + console.print(f"[blue]正在保存: {output_file}[/blue]") + if save_raw_config(config_data, output_file): + console.print(Panel("🎉 配置文件解密完成!", expand=False, border_style="bold green")) + return 0 + return 1 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except KeyboardInterrupt: + print("\n\n👋 已取消操作") + sys.exit(0) + except Exception as e: + print(f"\n❌ 程序执行出错: {str(e)}") + sys.exit(1) diff --git a/examples/test_cdn_url.py b/examples/test_cdn_url.py index d281b56..cca7dc3 100644 --- a/examples/test_cdn_url.py +++ b/examples/test_cdn_url.py @@ -24,9 +24,10 @@ def main(): # 测试不同版本的CDN URL构建 versions = ["0.29", "0.30"] - platforms = ["android", "ios"] + platforms = ["android", "ios", "ru"] - print(f"CDN基础URL: {config_manager.cdn_base_url}") + print(f"CDN基础URL(默认): {config_manager.cdn_config.get('base_url', '')}") + print(f"CDN基础URL(ru): {config_manager.cdn_config.get('ru_base_url', '')}") print(f"存储路径: {config_manager.storage_path}") print() @@ -41,7 +42,8 @@ def main(): # 显示当前配置 print(f"当前CDN配置:") - print(f"CDN基础URL: {config_manager.cdn_base_url}") + print(f"CDN基础URL(默认): {config_manager.cdn_config.get('base_url', '')}") + print(f"CDN基础URL(ru): {config_manager.cdn_config.get('ru_base_url', '')}") print(f"CDN超时: {config_manager.cdn_config.get('timeout', 10)}") print(f"CDN重试次数: {config_manager.cdn_config.get('retry_count', 3)}") diff --git a/examples/test_paths.py b/examples/test_paths.py index bfe23cb..a68aa75 100644 --- a/examples/test_paths.py +++ b/examples/test_paths.py @@ -24,7 +24,7 @@ def main(): # 测试不同版本的路径构建 versions = ["0.29", "0.30"] - platforms = ["android", "ios"] + platforms = ["android", "ios", "ru"] print(f"存储配置:") print(f" rclone_remote: {config_manager.storage_config.get('rclone_remote', 'remote')}") diff --git a/examples/test_rclone_path.py b/examples/test_rclone_path.py index 31b1be4..74397fe 100644 --- a/examples/test_rclone_path.py +++ b/examples/test_rclone_path.py @@ -24,7 +24,7 @@ def main(): # 测试不同版本的路径构建 versions = ["0.29", "0.30"] - platforms = ["android", "ios"] + platforms = ["android", "ios", "ru"] for version in versions: for platform in platforms: diff --git a/src/config_man/cli/main.py b/src/config_man/cli/main.py index 725786e..81dad10 100644 --- a/src/config_man/cli/main.py +++ b/src/config_man/cli/main.py @@ -27,7 +27,7 @@ def cli(): def view(version, json_format, storage_path, cdn_url, mock): """查看配置文件 - 对比显示对象存储和CDN中的配置内容,支持Android和iOS平台。 + 对比显示对象存储和CDN中的配置内容,支持 Android、iOS 和 RU 平台。 示例: config-man view --version 0.30 @@ -59,7 +59,7 @@ def view(version, json_format, storage_path, cdn_url, mock): @cli.command() -@click.option('--platform', required=True, type=click.Choice(['android', 'ios']), +@click.option('--platform', required=True, type=click.Choice(['android', 'ios', 'ru']), help='平台类型') @click.option('--target-version', required=True, help='目标版本号') @click.option('--update-version', required=True, help='更新版本号') @@ -74,6 +74,7 @@ def suggest_update(platform, target_version, update_version, storage_path, cdn_u 示例: config-man suggest-update --platform android --target-version 0.29 --update-version 0.30 config-man suggest-update --platform ios --target-version 0.29 --update-version 0.30 --mock + config-man suggest-update --platform ru --target-version 0.29 --update-version 0.30 --mock """ try: # 如果使用模拟环境,启用rclone模拟 @@ -100,7 +101,7 @@ def suggest_update(platform, target_version, update_version, storage_path, cdn_u @cli.command() -@click.option('--platform', required=True, type=click.Choice(['android', 'ios']), +@click.option('--platform', required=True, type=click.Choice(['android', 'ios', 'ru']), help='平台类型') @click.option('--target-version', required=True, help='目标版本号') @click.option('--update-version', required=True, help='更新版本号') @@ -115,6 +116,7 @@ def force_update(platform, target_version, update_version, storage_path, cdn_url 示例: config-man force-update --platform ios --target-version 0.29 --update-version 0.30 config-man force-update --platform android --target-version 0.29 --update-version 0.30 --mock + config-man force-update --platform ru --target-version 0.29 --update-version 0.30 --mock """ try: # 如果使用模拟环境,启用rclone模拟 diff --git a/src/config_man/core/config_manager.py b/src/config_man/core/config_manager.py index 2cf522c..b4923fc 100644 --- a/src/config_man/core/config_manager.py +++ b/src/config_man/core/config_manager.py @@ -13,9 +13,32 @@ class ConfigManager: def __init__(self, storage_path: str = None, cdn_base_url: str = None): # 使用配置文件中的值,如果参数为None self.storage_path = storage_path or config.get_storage_path() - self.cdn_base_url = cdn_base_url or config.get_cdn_base_url() + self.cdn_url_override = cdn_base_url self.storage_config = config.get_storage_config() self.cdn_config = config.get_cdn_config() + + def _storage_prefix_for_platform(self, platform: str) -> str: + """根据平台获取对象存储路径前缀""" + if platform == "ru": + return "ru" + return self.storage_path + + def _config_filename_for_platform(self, platform: str) -> str: + """根据平台获取配置文件名""" + if platform == "ru": + return "androidconfig.json" + return f"{platform}config.json" + + def _cdn_base_for_platform(self, platform: str) -> str: + """根据平台获取CDN基础URL""" + if self.cdn_url_override is not None: + return self.cdn_url_override + + if platform == "ru": + return self.cdn_config.get( + "ru_base_url", "https://128-ft-cdn-ali-tx.arksgame.com/ru" + ) + return config.get_cdn_base_url() def version_to_path(self, version: str) -> str: """将版本号转换为存储路径格式""" @@ -25,23 +48,26 @@ class ConfigManager: def get_config_path(self, version: str, platform: str) -> str: """获取配置文件在对象存储中的路径""" version_path = self.version_to_path(version) - filename = f"{platform}config.json" - return f"{self.storage_path}/{version_path}/{filename}" + filename = self._config_filename_for_platform(platform) + prefix = self._storage_prefix_for_platform(platform) + return f"{prefix}/{version_path}/{filename}" def get_rclone_path(self, version: str, platform: str) -> str: """获取rclone完整路径(包含远程名称)""" version_path = self.version_to_path(version) - filename = f"{platform}config.json" + filename = self._config_filename_for_platform(platform) + prefix = self._storage_prefix_for_platform(platform) rclone_remote = self.storage_config.get('rclone_remote', 'remote') - return f"{rclone_remote}:{self.storage_path}/{version_path}/{filename}" + return f"{rclone_remote}:{prefix}/{version_path}/{filename}" def get_cdn_url(self, version: str, platform: str) -> str: """获取配置文件在CDN中的URL""" - if not self.cdn_base_url: + cdn_base_url = self._cdn_base_for_platform(platform) + if not cdn_base_url: return "" version_path = self.version_to_path(version) - filename = f"{platform}config.json" - return f"{self.cdn_base_url}/{version_path}/{filename}" + filename = self._config_filename_for_platform(platform) + return f"{cdn_base_url}/{version_path}/{filename}" def download_from_storage(self, version: str, platform: str) -> Optional[str]: """从对象存储下载并解密配置文件""" diff --git a/src/config_man/core/display_manager.py b/src/config_man/core/display_manager.py index 16738da..ed592e3 100644 --- a/src/config_man/core/display_manager.py +++ b/src/config_man/core/display_manager.py @@ -14,13 +14,22 @@ class DisplayManager: self.console = Console() self.display_config = config.get_display_config() + def _platform_label(self, platform: str) -> str: + """平台显示名称""" + mapping = { + "android": "Android", + "ios": "iOS", + "ru": "RU", + } + return mapping.get(platform, platform) + def display_config_comparison(self, version: str, platform: str, storage_config: Dict, cdn_config: Dict, differences: Dict): """显示配置对比结果""" # 创建标题 - title = f"版本: {version} | 平台: {platform.title()}" + title = f"版本: {version} | 平台: {self._platform_label(platform)}" self.console.print(f"\n[bold blue]{title}[/bold blue]") # 创建表格 @@ -51,7 +60,7 @@ class DisplayManager: storage_config: Dict, cdn_config: Dict): """以JSON格式显示配置""" - title = f"版本: {version} | 平台: {platform.title()}" + title = f"版本: {version} | 平台: {self._platform_label(platform)}" self.console.print(f"\n[bold blue]{title}[/bold blue]") # 创建对比JSON @@ -79,7 +88,7 @@ class DisplayManager: self.console.print(f"\n[bold green]配置查看总结[/bold green]") self.console.print(f"版本: {version}") - for platform in ["android", "ios"]: + for platform in ["android", "ios", "ru"]: if platform in results: result = results[platform] if result["success"]: @@ -91,13 +100,13 @@ class DisplayManager: status = "✅ 正常" if differences == 0 else f"⚠️ {differences} 项不同" self.console.print( - f" {platform.title()}: {status} " + f" {self._platform_label(platform)}: {status} " f"(存储: {storage_keys}项, CDN: {cdn_keys}项)" ) else: - self.console.print(f" {platform.title()}: ❌ 失败") + self.console.print(f" {self._platform_label(platform)}: ❌ 失败") else: - self.console.print(f" {platform.title()}: ⚠️ 未检查") + self.console.print(f" {self._platform_label(platform)}: ⚠️ 未检查") def display_error(self, message: str): """显示错误信息""" diff --git a/src/config_man/core/force_update_command.py b/src/config_man/core/force_update_command.py index ba87d81..8b8c044 100644 --- a/src/config_man/core/force_update_command.py +++ b/src/config_man/core/force_update_command.py @@ -72,8 +72,8 @@ class ForceUpdateCommand: """验证输入参数""" # 验证平台 - if platform not in ["android", "ios"]: - self.display_manager.display_error(f"无效的平台: {platform},支持 android 或 ios") + if platform not in ["android", "ios", "ru"]: + self.display_manager.display_error(f"无效的平台: {platform},支持 android、ios 或 ru") return False # 验证版本号格式 diff --git a/src/config_man/core/suggest_update_command.py b/src/config_man/core/suggest_update_command.py index b4c9843..a9ce694 100644 --- a/src/config_man/core/suggest_update_command.py +++ b/src/config_man/core/suggest_update_command.py @@ -72,8 +72,8 @@ class SuggestUpdateCommand: """验证输入参数""" # 验证平台 - if platform not in ["android", "ios"]: - self.display_manager.display_error(f"无效的平台: {platform},支持 android 或 ios") + if platform not in ["android", "ios", "ru"]: + self.display_manager.display_error(f"无效的平台: {platform},支持 android、ios 或 ru") return False # 验证版本号格式 diff --git a/src/config_man/core/view_command.py b/src/config_man/core/view_command.py index 0d83cbe..0585ba3 100644 --- a/src/config_man/core/view_command.py +++ b/src/config_man/core/view_command.py @@ -23,8 +23,8 @@ class ViewCommand: results = {} success = True - # 检查Android和iOS平台 - for platform in ["android", "ios"]: + # 检查Android、iOS和俄罗斯平台 + for platform in ["android", "ios", "ru"]: result = self._check_platform_config(version, platform) results[platform] = result @@ -110,7 +110,7 @@ class ViewCommand: def _display_table_results(self, version: str, results: Dict): """以表格格式显示结果""" - for platform in ["android", "ios"]: + for platform in ["android", "ios", "ru"]: if platform in results: result = results[platform] if result["success"]: @@ -122,12 +122,12 @@ class ViewCommand: ) else: self.display_manager.display_error( - f"{platform.title()} 配置检查失败: {result['error']}" + f"{self.display_manager._platform_label(platform)} 配置检查失败: {result['error']}" ) def _display_json_results(self, version: str, results: Dict): """以JSON格式显示结果""" - for platform in ["android", "ios"]: + for platform in ["android", "ios", "ru"]: if platform in results: result = results[platform] if result["success"]: @@ -138,5 +138,5 @@ class ViewCommand: ) else: self.display_manager.display_error( - f"{platform.title()} 配置检查失败: {result['error']}" + f"{self.display_manager._platform_label(platform)} 配置检查失败: {result['error']}" ) \ No newline at end of file diff --git a/src/config_man/utils/config.py b/src/config_man/utils/config.py index 96d5393..05ba176 100644 --- a/src/config_man/utils/config.py +++ b/src/config_man/utils/config.py @@ -48,6 +48,7 @@ class Config: }, "cdn": { "base_url": "", + "ru_base_url": "https://128-ft-cdn-ali-tx.arksgame.com/ru", "timeout": 10, "retry_count": 3, "cloudflare": { @@ -100,6 +101,7 @@ class Config: "CONFIG_MAN_STORAGE_RCLONE_REMOTE": ("storage", "rclone_remote"), "CONFIG_MAN_STORAGE_TIMEOUT": ("storage", "timeout"), "CONFIG_MAN_CDN_BASE_URL": ("cdn", "base_url"), + "CONFIG_MAN_CDN_RU_BASE_URL": ("cdn", "ru_base_url"), "CONFIG_MAN_CDN_TIMEOUT": ("cdn", "timeout"), "CONFIG_MAN_CDN_RETRY_COUNT": ("cdn", "retry_count"), "CONFIG_MAN_CDN_CLOUDFLARE_API_TOKEN": ("cdn", "cloudflare", "api_token"), diff --git a/src/config_man/utils/mock_rclone.py b/src/config_man/utils/mock_rclone.py index ba147d8..fb2b8f3 100644 --- a/src/config_man/utils/mock_rclone.py +++ b/src/config_man/utils/mock_rclone.py @@ -50,6 +50,19 @@ class MockRclone: "RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358", "FuncUrl": "https://leaderboardcreate.azurewebsites.net", "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==" + }, + "ru": { + "EventApiURL": "https://n3backend.azurewebsites.net/", + "Ver": "0.29.1ru123", + "PlayFabTitle": "B066F", + "Google_Play_URL": "https://play.google.com/store/apps/details?id=com.arkgame.ft", + "APP_Store_URL": "https://apps.apple.com/us/app/id6505145935", + "HeartBeat": "60", + "RTMPid": "80000586", + "RTMServerEndpoint": "rtm-intl-frontgate.ilivedata.com:13321", + "RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358", + "FuncUrl": "https://leaderboardcreate.azurewebsites.net", + "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==" } }, "0.30": { @@ -80,6 +93,20 @@ class MockRclone: "FuncUrl": "https://leaderboardcreate.azurewebsites.net", "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==", "NewFeature": "enabled" + }, + "ru": { + "EventApiURL": "https://n3backend.azurewebsites.net/", + "Ver": "0.30.2ru789", + "PlayFabTitle": "B066F", + "Google_Play_URL": "https://play.google.com/store/apps/details?id=com.arkgame.ft", + "APP_Store_URL": "https://apps.apple.com/us/app/id6505145935", + "HeartBeat": "60", + "RTMPid": "80000586", + "RTMServerEndpoint": "rtm-intl-frontgate.ilivedata.com:13321", + "RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358", + "FuncUrl": "https://leaderboardcreate.azurewebsites.net", + "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==", + "NewFeature": "enabled" } } } @@ -89,12 +116,18 @@ class MockRclone: # 创建测试目录结构 for version, platforms in test_configs.items(): - version_path = f"{self.test_data_path}/{storage_path}/{version.replace('.', '_')}" - os.makedirs(version_path, exist_ok=True) + version_token = version.replace('.', '_') for platform, config_data in platforms.items(): - filename = f"{platform}config.json" - filepath = os.path.join(version_path, filename) + if platform == "ru": + platform_dir = f"{self.test_data_path}/ru/{version_token}" + filename = "androidconfig.json" + else: + platform_dir = f"{self.test_data_path}/{storage_path}/{version_token}" + filename = f"{platform}config.json" + + os.makedirs(platform_dir, exist_ok=True) + filepath = os.path.join(platform_dir, filename) # 加密配置并保存 config_json = json.dumps(config_data, indent=2, ensure_ascii=False) diff --git a/tests/fixtures/test_configs.py b/tests/fixtures/test_configs.py index ef66432..c16f33e 100644 --- a/tests/fixtures/test_configs.py +++ b/tests/fixtures/test_configs.py @@ -38,6 +38,19 @@ def create_test_configs(): "RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358", "FuncUrl": "https://leaderboardcreate.azurewebsites.net", "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==" + }, + "ru": { + "EventApiURL": "https://n3backend.azurewebsites.net/", + "Ver": "0.29.1ru123", + "PlayFabTitle": "B066F", + "Google_Play_URL": "https://play.google.com/store/apps/details?id=com.arkgame.ft", + "APP_Store_URL": "https://apps.apple.com/us/app/id6505145935", + "HeartBeat": "60", + "RTMPid": "80000586", + "RTMServerEndpoint": "rtm-intl-frontgate.ilivedata.com:13321", + "RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358", + "FuncUrl": "https://leaderboardcreate.azurewebsites.net", + "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==" } }, "0.30": { @@ -68,17 +81,37 @@ def create_test_configs(): "FuncUrl": "https://leaderboardcreate.azurewebsites.net", "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==", "NewFeature": "enabled" + }, + "ru": { + "EventApiURL": "https://n3backend.azurewebsites.net/", + "Ver": "0.30.2ru789", + "PlayFabTitle": "B066F", + "Google_Play_URL": "https://play.google.com/store/apps/details?id=com.arkgame.ft", + "APP_Store_URL": "https://apps.apple.com/us/app/id6505145935", + "HeartBeat": "60", + "RTMPid": "80000586", + "RTMServerEndpoint": "rtm-intl-frontgate.ilivedata.com:13321", + "RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358", + "FuncUrl": "https://leaderboardcreate.azurewebsites.net", + "FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w==", + "NewFeature": "enabled" } } } # 创建测试目录结构 for version, platforms in test_configs.items(): - version_path = f"test_data/ab/{version.replace('.', '_')}" - os.makedirs(version_path, exist_ok=True) + version_token = version.replace('.', '_') for platform, config in platforms.items(): - filename = f"{platform}config.json" + if platform == "ru": + version_path = f"test_data/ru/{version_token}" + filename = "androidconfig.json" + else: + version_path = f"test_data/ab/{version_token}" + filename = f"{platform}config.json" + + os.makedirs(version_path, exist_ok=True) filepath = os.path.join(version_path, filename) # 加密配置并保存