Files
edict/scripts/utils.py
cft0808 ae3942bc87 fix: 兼容 Windows 系统的 Python 解释器路径查找
在 Windows 上不存在 python3 命令,subprocess 调用会抛出 [WinError 2]。
在 utils.py 中新增 python_bin() 函数,优先使用 sys.executable(当前解释器路径),
回退到 shutil.which('python3') / shutil.which('python'),确保全平台兼容。
server.py 和 kanban_update.py 中所有硬编码的 'python3' 替换为 python_bin()。

Closes #289
2026-04-27 22:42:06 +08:00

70 lines
2.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
三省六部 · 公共工具函数
避免 read_json / now_iso 等基础函数在多个脚本中重复定义
"""
import os
import sys
import json, pathlib, datetime, shutil
def read_json(path, default=None):
"""安全读取 JSON 文件,失败返回 default"""
try:
return json.loads(pathlib.Path(path).read_text(encoding='utf-8'))
except Exception:
return default if default is not None else {}
def get_openclaw_home() -> pathlib.Path:
"""Return OpenClaw home directory, respecting OPENCLAW_HOME env var."""
env = os.environ.get('OPENCLAW_HOME')
if env:
return pathlib.Path(env).expanduser()
return pathlib.Path.home() / '.openclaw'
def now_iso():
"""返回 UTC ISO 8601 时间字符串(末尾 Z"""
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')
def today_str(fmt='%Y%m%d'):
"""返回今天日期字符串,默认 YYYYMMDD"""
return datetime.date.today().strftime(fmt)
def safe_name(s: str) -> bool:
"""检查名称是否只含安全字符(字母、数字、下划线、连字符、中文)"""
import re
return bool(re.match(r'^[a-zA-Z0-9_\-\u4e00-\u9fff]+$', s))
def python_bin() -> str:
"""返回当前 Python 解释器路径,兼容 Windows无 python3 命令)"""
return sys.executable or shutil.which('python3') or shutil.which('python') or 'python3'
def validate_url(url: str, allowed_schemes=('https',), allowed_domains=None) -> bool:
"""校验 URL 合法性,防 SSRF"""
from urllib.parse import urlparse
try:
parsed = urlparse(url)
if parsed.scheme not in allowed_schemes:
return False
if allowed_domains and parsed.hostname not in allowed_domains:
return False
if not parsed.hostname:
return False
# 禁止内网地址
import ipaddress
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback or ip.is_reserved:
return False
except ValueError:
pass # hostname 不是 IP放行
return True
except Exception:
return False