diff --git a/.github/workflows/create-prerelease.yml b/.github/workflows/create-prerelease.yml new file mode 100644 index 000000000..b48a894d9 --- /dev/null +++ b/.github/workflows/create-prerelease.yml @@ -0,0 +1,32 @@ +# 当代码推送到 master 分支时,自动创建一个 pre-release + +name: Create Pre-release + +on: + push: + branches: + - master + +jobs: + create-prerelease: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # 获取所有提交历史,以便生成 release notes + fetch-depth: 0 + + - name: Generate tag name + id: generate_tag + run: echo "TAG_NAME=prerelease-$(date -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + + - name: Create Pre-release + env: + # 使用仓库自带的 GITHUB_TOKEN 进行认证 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create ${{ steps.generate_tag.outputs.TAG_NAME }} \ + --title "Pre-release ${{ steps.generate_tag.outputs.TAG_NAME }}" \ + --prerelease \ + --generate-notes \ No newline at end of file diff --git a/README.md b/README.md index 390b3da92..c4c803055 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ ## 📖 项目介绍 **MoFox_Bot** 是一个基于 [MaiCore](https://github.com/MaiM-with-u/MaiBot) `0.10.0 snapshot.5` 版本的增强型 `fork` 项目。 -我们在保留原版所有功能的基础上,进行了一系列的改进和功能拓展,致力于提供更强的稳定性、更丰富的功能和更流畅的用户体验。 +我们在保留原版所有功能的基础上,进行了一系列的改进和功能拓展,致力于提供更强的稳定性、更丰富的功能和更流畅的用户体验 > [!IMPORTANT] > **第三方项目声明** diff --git a/bot.py b/bot.py index aab5cd4f1..2490e6a97 100644 --- a/bot.py +++ b/bot.py @@ -1,6 +1,5 @@ # import asyncio import asyncio -import hashlib import os import sys import time @@ -9,6 +8,7 @@ import traceback from pathlib import Path from rich.traceback import install from colorama import init, Fore +from dotenv import load_dotenv # 处理.env文件 # maim_message imports for console input @@ -34,6 +34,28 @@ script_dir = os.path.dirname(os.path.abspath(__file__)) os.chdir(script_dir) logger.info(f"已设置工作目录为: {script_dir}") +# 检查并创建.env文件 +def ensure_env_file(): + """确保.env文件存在,如果不存在则从模板创建""" + env_file = Path(".env") + template_env = Path("template/template.env") + + if not env_file.exists(): + if template_env.exists(): + logger.info("未找到.env文件,正在从模板创建...") + import shutil + shutil.copy(template_env, env_file) + logger.info("已从template/template.env创建.env文件") + logger.warning("请编辑.env文件,将EULA_CONFIRMED设置为true并配置其他必要参数") + else: + logger.error("未找到.env文件和template.env模板文件") + sys.exit(1) + +# 确保环境文件存在 +ensure_env_file() + +# 加载环境变量 +load_dotenv() confirm_logger = get_logger("confirm") # 获取没有加载env时的环境变量 @@ -105,82 +127,41 @@ async def graceful_shutdown(): logger.error(f"麦麦关闭失败: {e}", exc_info=True) -def _calculate_file_hash(file_path: Path, file_type: str) -> str: - """计算文件的MD5哈希值""" - if not file_path.exists(): - logger.error(f"{file_type} 文件不存在") - raise FileNotFoundError(f"{file_type} 文件不存在") - - with open(file_path, "r", encoding="utf-8") as f: - content = f.read() - return hashlib.md5(content.encode("utf-8")).hexdigest() - - -def _check_agreement_status(file_hash: str, confirm_file: Path, env_var: str) -> tuple[bool, bool]: - """检查协议确认状态 - - Returns: - tuple[bool, bool]: (已确认, 未更新) - """ - # 检查环境变量确认 - if file_hash == os.getenv(env_var): - return True, False - - # 检查确认文件 - if confirm_file.exists(): - with open(confirm_file, "r", encoding="utf-8") as f: - confirmed_content = f.read() - if file_hash == confirmed_content: - return True, False - - return False, True - - -def _prompt_user_confirmation(eula_hash: str, privacy_hash: str) -> None: - """提示用户确认协议""" - confirm_logger.critical("EULA或隐私条款内容已更新,请在阅读后重新确认,继续运行视为同意更新后的以上两款协议") - confirm_logger.critical( - f'输入"同意"或"confirmed"或设置环境变量"EULA_AGREE={eula_hash}"和"PRIVACY_AGREE={privacy_hash}"继续运行' - ) - - while True: - user_input = input().strip().lower() - if user_input in ["同意", "confirmed"]: - return - confirm_logger.critical('请输入"同意"或"confirmed"以继续运行') - - -def _save_confirmations(eula_updated: bool, privacy_updated: bool, eula_hash: str, privacy_hash: str) -> None: - """保存用户确认结果""" - if eula_updated: - logger.info(f"更新EULA确认文件{eula_hash}") - Path("eula.confirmed").write_text(eula_hash, encoding="utf-8") - - if privacy_updated: - logger.info(f"更新隐私条款确认文件{privacy_hash}") - Path("privacy.confirmed").write_text(privacy_hash, encoding="utf-8") - - def check_eula(): - """检查EULA和隐私条款确认状态""" - # 计算文件哈希值 - eula_hash = _calculate_file_hash(Path("EULA.md"), "EULA.md") - privacy_hash = _calculate_file_hash(Path("PRIVACY.md"), "PRIVACY.md") - - # 检查确认状态 - eula_confirmed, eula_updated = _check_agreement_status(eula_hash, Path("eula.confirmed"), "EULA_AGREE") - privacy_confirmed, privacy_updated = _check_agreement_status( - privacy_hash, Path("privacy.confirmed"), "PRIVACY_AGREE" - ) - - # 早期返回:如果都已确认且未更新 - if eula_confirmed and privacy_confirmed: + """检查EULA和隐私条款确认状态 - 环境变量版(类似Minecraft)""" + # 检查环境变量中的EULA确认 + eula_confirmed = os.getenv('EULA_CONFIRMED', '').lower() + + if eula_confirmed == 'true': + logger.info("EULA已通过环境变量确认") return - - # 如果有更新,需要重新确认 - if eula_updated or privacy_updated: - _prompt_user_confirmation(eula_hash, privacy_hash) - _save_confirmations(eula_updated, privacy_updated, eula_hash, privacy_hash) + + # 如果没有确认,提示用户 + confirm_logger.critical("您需要同意EULA和隐私条款才能使用MoFox_Bot") + confirm_logger.critical("请阅读以下文件:") + confirm_logger.critical(" - EULA.md (用户许可协议)") + confirm_logger.critical(" - PRIVACY.md (隐私条款)") + confirm_logger.critical("然后编辑 .env 文件,将 'EULA_CONFIRMED=false' 改为 'EULA_CONFIRMED=true'") + + # 等待用户确认 + while True: + try: + load_dotenv(override=True) # 重新加载.env文件 + + eula_confirmed = os.getenv('EULA_CONFIRMED', '').lower() + if eula_confirmed == 'true': + confirm_logger.info("EULA确认成功,感谢您的同意") + return + + confirm_logger.critical("请修改 .env 文件中的 EULA_CONFIRMED=true 后重新启动程序") + input("按Enter键检查.env文件状态...") + + except KeyboardInterrupt: + confirm_logger.info("用户取消,程序退出") + sys.exit(0) + except Exception as e: + confirm_logger.error(f"检查EULA状态失败: {e}") + sys.exit(1) class MaiBotMain(BaseMain): diff --git a/src/chat/chat_loop/cycle_processor.py b/src/chat/chat_loop/cycle_processor.py index 0af7b1b85..3748c5780 100644 --- a/src/chat/chat_loop/cycle_processor.py +++ b/src/chat/chat_loop/cycle_processor.py @@ -201,11 +201,11 @@ class CycleProcessor: result = await event_manager.trigger_event( EventType.ON_PLAN, permission_group="SYSTEM", stream_id=self.context.chat_stream ) - if not result.all_continue_process(): + if result and not result.all_continue_process(): raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成") with Timer("规划器", cycle_timers): actions, _ = await self.action_planner.plan(mode=mode) - + async def execute_action(action_info): """执行单个动作的通用函数""" try: @@ -298,9 +298,12 @@ class CycleProcessor: if reply_actions: logger.info(f"{self.log_prefix} 正在执行文本回复...") for action in reply_actions: - target_user_id = action.get("action_message",{}).get("chat_info_user_id","") - action_message_test =action.get("action_message",{}) - logger.info(f"如果你探到这条日志请把它复制下来发到Q群里,如果你探到这条日志请把它复制下来发到Q群里,如果你探到这条日志请把它复制下来发到Q群里,调试内容:{action_message_test}") + action_message = action.get("action_message") + if not action_message: + logger.warning(f"{self.log_prefix} reply 动作缺少 action_message,跳过") + continue + + target_user_id = action_message.get("chat_info_user_id","") if target_user_id == global_config.bot.qq_account and not global_config.chat.allow_reply_self: logger.warning("选取的reply的目标为bot自己,跳过reply action") continue diff --git a/src/chat/planner_actions/plan_filter.py b/src/chat/planner_actions/plan_filter.py index f56bcf60e..d76f1aa04 100644 --- a/src/chat/planner_actions/plan_filter.py +++ b/src/chat/planner_actions/plan_filter.py @@ -42,15 +42,18 @@ class PlanFilter: """ 执行筛选逻辑,并填充 Plan 对象的 decided_actions 字段。 """ + logger.debug(f"墨墨在这里加了日志 -> filter 入口 plan: {plan}") try: prompt, used_message_id_list = await self._build_prompt(plan) plan.llm_prompt = prompt + logger.debug(f"墨墨在这里加了日志 -> LLM prompt: {prompt}") llm_content, _ = await self.planner_llm.generate_response_async(prompt=prompt) if llm_content: - logger.debug(f"LLM a原始返回: {llm_content}") + logger.debug(f"墨墨在这里加了日志 -> LLM a原始返回: {llm_content}") parsed_json = orjson.loads(repair_json(llm_content)) + logger.debug(f"墨墨在这里加了日志 -> 解析后的 JSON: {parsed_json}") if isinstance(parsed_json, dict): parsed_json = [parsed_json] @@ -91,7 +94,8 @@ class PlanFilter: plan.decided_actions = [ ActionPlannerInfo(action_type="no_action", reasoning=f"筛选时出错: {e}") ] - + + logger.debug(f"墨墨在这里加了日志 -> filter 出口 decided_actions: {plan.decided_actions}") return plan async def _build_prompt(self, plan: Plan) -> tuple[str, list]: diff --git a/src/common/message/api.py b/src/common/message/api.py index 75e5c84e9..d24574d6e 100644 --- a/src/common/message/api.py +++ b/src/common/message/api.py @@ -3,6 +3,7 @@ import importlib.metadata from maim_message import MessageServer from src.common.logger import get_logger from src.config.config import global_config +import os global_api = None @@ -22,9 +23,18 @@ def get_global_api() -> MessageServer: # sourcery skip: extract-method maim_message_config = global_config.maim_message # 设置基本参数 + + host = os.getenv("HOST", "127.0.0.1") + port_str = os.getenv("PORT", "8000") + + try: + port = int(port_str) + except ValueError: + port = 8000 + kwargs = { - "host": global_config.server.host, - "port": int(global_config.server.port), + "host": host, + "port": port, "app": get_global_server().get_app(), } diff --git a/src/common/remote.py b/src/common/remote.py index f38791f7f..2aa750449 100644 --- a/src/common/remote.py +++ b/src/common/remote.py @@ -1,9 +1,13 @@ import asyncio +import base64 +import json import aiohttp import platform from datetime import datetime, timezone +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa from src.common.logger import get_logger from src.common.tcp_connector import get_tcp_connector from src.config.config import global_config @@ -24,9 +28,12 @@ class TelemetryHeartBeatTask(AsyncTask): self.server_url = TELEMETRY_SERVER_URL """遥测服务地址""" - self.client_uuid: str | None = local_storage["mmc_uuid"] if "mmc_uuid" in local_storage else None # type: ignore + self.client_uuid: str | None = local_storage["mofox_uuid"] if "mofox_uuid" in local_storage else None # type: ignore """客户端UUID""" + self.private_key_pem: str | None = local_storage["mofox_private_key"] if "mofox_private_key" in local_storage else None # type: ignore + """客户端私钥""" + self.info_dict = self._get_sys_info() """系统信息字典""" @@ -36,7 +43,7 @@ class TelemetryHeartBeatTask(AsyncTask): info_dict = { "os_type": "Unknown", "py_version": platform.python_version(), - "mmc_version": global_config.MMC_VERSION, + "mofox_version": global_config.MMC_VERSION, } match platform.system(): @@ -51,77 +58,224 @@ class TelemetryHeartBeatTask(AsyncTask): return info_dict + def _generate_signature(self, request_body: dict) -> tuple[str, str]: + """ + 生成RSA签名 + + Returns: + tuple[str, str]: (timestamp, signature_b64) + """ + if not self.private_key_pem: + raise ValueError("私钥未初始化") + + # 生成时间戳 + timestamp = datetime.now(timezone.utc).isoformat() + + # 创建签名数据字符串 + sign_data = f"{self.client_uuid}:{timestamp}:{json.dumps(request_body, separators=(',', ':'))}" + + # 加载私钥 + private_key = serialization.load_pem_private_key( + self.private_key_pem.encode('utf-8'), + password=None + ) + + # 确保是RSA私钥 + if not isinstance(private_key, rsa.RSAPrivateKey): + raise ValueError("私钥必须是RSA格式") + + # 生成签名 + signature = private_key.sign( + sign_data.encode('utf-8'), + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + + # Base64编码 + signature_b64 = base64.b64encode(signature).decode('utf-8') + + return timestamp, signature_b64 + + def _decrypt_challenge(self, challenge_b64: str) -> str: + """ + 解密挑战数据 + + Args: + challenge_b64: Base64编码的挑战数据 + + Returns: + str: 解密后的UUID字符串 + """ + if not self.private_key_pem: + raise ValueError("私钥未初始化") + + # 加载私钥 + private_key = serialization.load_pem_private_key( + self.private_key_pem.encode('utf-8'), + password=None + ) + + # 确保是RSA私钥 + if not isinstance(private_key, rsa.RSAPrivateKey): + raise ValueError("私钥必须是RSA格式") + + # 解密挑战数据 + decrypted_bytes = private_key.decrypt( + base64.b64decode(challenge_b64), + padding.OAEP( + mgf=padding.MGF1(hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + + return decrypted_bytes.decode('utf-8') + async def _req_uuid(self) -> bool: """ - 向服务端请求UUID(不应在已存在UUID的情况下调用,会覆盖原有的UUID) + 向服务端请求UUID和私钥(两步注册流程) """ - - if "deploy_time" not in local_storage: - logger.error("本地存储中缺少部署时间,无法请求UUID") - return False - try_count: int = 0 while True: - # 如果不存在,则向服务端请求一个新的UUID(注册客户端) - logger.info("正在向遥测服务端请求UUID...") + logger.info("正在向遥测服务端注册客户端...") try: async with aiohttp.ClientSession(connector=await get_tcp_connector()) as session: + # Step 1: 获取临时UUID、私钥和挑战数据 + logger.debug("开始注册步骤1:获取临时UUID和私钥") async with session.post( - f"{TELEMETRY_SERVER_URL}/stat/reg_client", - json={"deploy_time": datetime.fromtimestamp(local_storage["deploy_time"], tz=timezone.utc).isoformat()}, - timeout=aiohttp.ClientTimeout(total=5), # 设置超时时间为5秒 + f"{TELEMETRY_SERVER_URL}/stat/reg_client_step1", + json={}, + timeout=aiohttp.ClientTimeout(total=5), ) as response: - logger.debug(f"{TELEMETRY_SERVER_URL}/stat/reg_client") - logger.debug(local_storage["deploy_time"]) # type: ignore - logger.debug(f"Response status: {response.status}") + logger.debug(f"Step1 Response status: {response.status}") + + if response.status != 200: + response_text = await response.text() + logger.error( + f"注册步骤1失败,状态码: {response.status}, 响应内容: {response_text}" + ) + raise aiohttp.ClientResponseError( + request_info=response.request_info, + history=response.history, + status=response.status, + message=f"Step1 failed: {response_text}" + ) + + step1_data = await response.json() + temp_uuid = step1_data.get("temp_uuid") + private_key = step1_data.get("private_key") + challenge = step1_data.get("challenge") + + if not all([temp_uuid, private_key, challenge]): + logger.error("Step1响应缺少必要字段:temp_uuid, private_key 或 challenge") + raise ValueError("Step1响应数据不完整") + + # 临时保存私钥用于解密 + self.private_key_pem = private_key + + # 解密挑战数据 + logger.debug("解密挑战数据...") + try: + decrypted_uuid = self._decrypt_challenge(challenge) + except Exception as e: + logger.error(f"解密挑战数据失败: {e}") + raise + + # 验证解密结果 + if decrypted_uuid != temp_uuid: + logger.error(f"解密结果验证失败: 期望 {temp_uuid}, 实际 {decrypted_uuid}") + raise ValueError("解密结果与临时UUID不匹配") + + logger.debug("挑战数据解密成功,开始注册步骤2") + + # Step 2: 发送解密结果完成注册 + async with session.post( + f"{TELEMETRY_SERVER_URL}/stat/reg_client_step2", + json={ + "temp_uuid": temp_uuid, + "decrypted_uuid": decrypted_uuid + }, + timeout=aiohttp.ClientTimeout(total=5), + ) as response: + logger.debug(f"Step2 Response status: {response.status}") if response.status == 200: - data = await response.json() - if client_id := data.get("mmc_uuid"): - # 将UUID存储到本地 - local_storage["mmc_uuid"] = client_id - self.client_uuid = client_id - logger.info(f"成功获取UUID: {self.client_uuid}") - return True # 成功获取UUID,返回True + step2_data = await response.json() + mofox_uuid = step2_data.get("mofox_uuid") + + if mofox_uuid: + # 将正式UUID和私钥存储到本地 + local_storage["mofox_uuid"] = mofox_uuid + local_storage["mofox_private_key"] = private_key + self.client_uuid = mofox_uuid + self.private_key_pem = private_key + logger.info(f"成功注册客户端,UUID: {self.client_uuid}") + return True else: - logger.error("无效的服务端响应") + logger.error("Step2响应缺少mofox_uuid字段") + raise ValueError("Step2响应数据不完整") + elif response.status in [400, 401]: + # 临时数据无效,需要重新开始 + response_text = await response.text() + logger.warning(f"Step2失败,临时数据无效: {response.status}, {response_text}") + raise ValueError(f"Step2失败: {response_text}") else: response_text = await response.text() logger.error( - f"请求UUID失败,不过你还是可以正常使用墨狐,状态码: {response.status}, 响应内容: {response_text}" + f"注册步骤2失败,状态码: {response.status}, 响应内容: {response_text}" ) + raise aiohttp.ClientResponseError( + request_info=response.request_info, + history=response.history, + status=response.status, + message=f"Step2 failed: {response_text}" + ) + except Exception as e: import traceback error_msg = str(e) or "未知错误" logger.warning( - f"请求UUID出错,不过你还是可以正常使用墨狐: {type(e).__name__}: {error_msg}" - ) # 可能是网络问题 + f"注册客户端出错,不过你还是可以正常使用墨狐: {type(e).__name__}: {error_msg}" + ) logger.debug(f"完整错误信息: {traceback.format_exc()}") # 请求失败,重试次数+1 try_count += 1 if try_count > 3: # 如果超过3次仍然失败,则退出 - logger.error("获取UUID失败,请检查网络连接或服务端状态") + logger.error("注册客户端失败,请检查网络连接或服务端状态") return False else: # 如果可以重试,等待后继续(指数退避) - logger.info(f"获取UUID失败,将于 {4**try_count} 秒后重试...") + logger.info(f"注册客户端失败,将于 {4**try_count} 秒后重试...") await asyncio.sleep(4**try_count) async def _send_heartbeat(self): """向服务器发送心跳""" - headers = { - "Client-UUID": self.client_uuid, - "User-Agent": f"HeartbeatClient/{self.client_uuid[:8]}", # type: ignore - } - - logger.debug(f"正在发送心跳到服务器: {self.server_url}") - logger.debug(str(headers)) + if not self.client_uuid or not self.private_key_pem: + logger.error("UUID或私钥未初始化,无法发送心跳") + return try: + # 生成签名 + timestamp, signature = self._generate_signature(self.info_dict) + + headers = { + "X-mofox-UUID": self.client_uuid, + "X-mofox-Signature": signature, + "X-mofox-Timestamp": timestamp, + "User-Agent": f"MofoxClient/{self.client_uuid[:8]}", + "Content-Type": "application/json" + } + + logger.debug(f"正在发送心跳到服务器: {self.server_url}") + logger.debug(f"Headers: {headers}") + async with aiohttp.ClientSession(connector=await get_tcp_connector()) as session: async with session.post( f"{self.server_url}/stat/client_heartbeat", @@ -135,31 +289,62 @@ class TelemetryHeartBeatTask(AsyncTask): if 200 <= response.status < 300: # 成功 logger.debug(f"心跳发送成功,状态码: {response.status}") - elif response.status == 403: - # 403 Forbidden + elif response.status == 401: + # 401 Unauthorized - 签名验证失败 logger.warning( - "(此消息不会影响正常使用)心跳发送失败,403 Forbidden: 可能是UUID无效或未注册。" - "处理措施:重置UUID,下次发送心跳时将尝试重新注册。" + "(此消息不会影响正常使用)心跳发送失败,401 Unauthorized: 签名验证失败。" + "处理措施:重置客户端信息,下次发送心跳时将尝试重新注册。" ) self.client_uuid = None - del local_storage["mmc_uuid"] # 删除本地存储的UUID + self.private_key_pem = None + if "mofox_uuid" in local_storage: + del local_storage["mofox_uuid"] + if "mofox_private_key" in local_storage: + del local_storage["mofox_private_key"] + elif response.status == 404: + # 404 Not Found - 客户端未注册 + logger.warning( + "(此消息不会影响正常使用)心跳发送失败,404 Not Found: 客户端未注册。" + "处理措施:重置客户端信息,下次发送心跳时将尝试重新注册。" + ) + self.client_uuid = None + self.private_key_pem = None + if "mofox_uuid" in local_storage: + del local_storage["mofox_uuid"] + if "mofox_private_key" in local_storage: + del local_storage["mofox_private_key"] + elif response.status == 403: + # 403 Forbidden - UUID无效或未注册 + response_text = await response.text() + logger.warning( + f"(此消息不会影响正常使用)心跳发送失败,403 Forbidden: UUID无效或未注册。" + f"响应内容: {response_text}。" + "处理措施:重置客户端信息,下次发送心跳时将尝试重新注册。" + ) + self.client_uuid = None + self.private_key_pem = None + if "mofox_uuid" in local_storage: + del local_storage["mofox_uuid"] + if "mofox_private_key" in local_storage: + del local_storage["mofox_private_key"] else: # 其他错误 response_text = await response.text() logger.warning( - f"(此消息不会影响正常使用)状态未发送,状态码: {response.status}, 响应内容: {response_text}" + f"(此消息不会影响正常使用)心跳发送失败,状态码: {response.status}, 响应内容: {response_text}" ) except Exception as e: import traceback error_msg = str(e) or "未知错误" - logger.warning(f"(此消息不会影响正常使用)状态未发生: {type(e).__name__}: {error_msg}") + logger.warning(f"(此消息不会影响正常使用)心跳发送出错: {type(e).__name__}: {error_msg}") logger.debug(f"完整错误信息: {traceback.format_exc()}") async def run(self): - # 发送心跳 - if self.client_uuid is None and not await self._req_uuid(): - logger.warning("获取UUID失败,跳过此次心跳") - return + # 检查是否已注册 + if not self.client_uuid or not self.private_key_pem: + if not await self._req_uuid(): + logger.warning("客户端注册失败,跳过此次心跳") + return await self._send_heartbeat() \ No newline at end of file diff --git a/src/common/server.py b/src/common/server.py index 30c55d72d..3263589a2 100644 --- a/src/common/server.py +++ b/src/common/server.py @@ -4,6 +4,7 @@ from typing import Optional from uvicorn import Config, Server as UvicornServer from src.config.config import global_config from rich.traceback import install +import os install(extra_lines=3) @@ -98,5 +99,14 @@ def get_global_server() -> Server: """获取全局服务器实例""" global global_server if global_server is None: - global_server = Server(host=global_config.server.host,port=int(global_config.server.port),) + + host = os.getenv("HOST", "127.0.0.1") + port_str = os.getenv("PORT", "8000") + + try: + port = int(port_str) + except ValueError: + port = 8000 + + global_server = Server(host=host, port=port) return global_server diff --git a/src/config/config.py b/src/config/config.py index a38122300..933b7e567 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -43,8 +43,7 @@ from src.config.official_configs import ( CrossContextConfig, PermissionConfig, CommandConfig, - PlanningSystemConfig, - ServerConfig, + PlanningSystemConfig ) from .api_ada_configs import ( @@ -399,7 +398,6 @@ class Config(ValidatedConfigBase): cross_context: CrossContextConfig = Field( default_factory=lambda: CrossContextConfig(), description="跨群聊上下文共享配置" ) - server: ServerConfig = Field(default_factory=lambda: ServerConfig(), description="主服务器配置") class APIAdapterConfig(ValidatedConfigBase): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index f9945cc50..be51a21e3 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -477,12 +477,6 @@ class ExperimentalConfig(ValidatedConfigBase): pfc_chatting: bool = Field(default=False, description="启用PFC聊天") -class ServerConfig(ValidatedConfigBase): - """主服务器配置类""" - - host: str = Field(default="127.0.0.1", description="主服务器监听地址") - port: int = Field(default=8080, description="主服务器监听端口") - class MaimMessageConfig(ValidatedConfigBase): """maim_message配置类""" diff --git a/src/main.py b/src/main.py index 247c91536..103884c10 100644 --- a/src/main.py +++ b/src/main.py @@ -5,6 +5,7 @@ import signal import sys from maim_message import MessageServer +from src.common.remote import TelemetryHeartBeatTask from src.manager.async_task_manager import async_task_manager from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask from src.common.remote import TelemetryHeartBeatTask diff --git a/template/template.env b/template/template.env index d9b6e2bd1..c838bcaa8 100644 --- a/template/template.env +++ b/template/template.env @@ -1,2 +1,3 @@ HOST=127.0.0.1 -PORT=8000 \ No newline at end of file +PORT=8000 +EULA_CONFIRMED=false \ No newline at end of file