feat(config, email_service, telegram_bot): Add dynamic GPTMail API key management and config reload capability
- Add reload_config() function to dynamically reload config.toml and team.json without restart - Implement GPTMail API key management with support for multiple keys (api_keys list) - Add functions to manage GPTMail keys: get_gptmail_keys(), add_gptmail_key(), remove_gptmail_key() - Add key rotation strategies: get_next_gptmail_key() (round-robin) and get_random_gptmail_key() - Add gptmail_keys.json file support for dynamic key management - Fix config parsing to handle include_team_owners and proxy settings in multiple locations - Update email_service.py to use key rotation for GPTMail API calls - Update telegram_bot.py to support dynamic key management - Add install_service.sh script for service installation - Update config.toml.example with new api_keys configuration option - Improve configuration flexibility by supporting both old (api_key) and new (api_keys) formats
This commit is contained in:
200
config.py
200
config.py
@@ -211,6 +211,110 @@ def save_team_json():
|
||||
_log_config("ERROR", "team.json", "保存失败", str(e))
|
||||
return False
|
||||
|
||||
|
||||
def reload_config() -> dict:
|
||||
"""重新加载配置文件 (config.toml 和 team.json)
|
||||
|
||||
Returns:
|
||||
dict: 重载结果,包含 success, teams_count, config_updated, message
|
||||
"""
|
||||
global _cfg, _raw_teams, TEAMS
|
||||
global EMAIL_PROVIDER, INCLUDE_TEAM_OWNERS, AUTH_PROVIDER
|
||||
global BROWSER_HEADLESS, ACCOUNTS_PER_TEAM
|
||||
global GPTMAIL_API_KEYS, GPTMAIL_DOMAINS, GPTMAIL_PREFIX
|
||||
global PROXY_ENABLED, PROXIES
|
||||
global S2A_API_BASE, S2A_ADMIN_KEY, S2A_ADMIN_TOKEN
|
||||
global S2A_CONCURRENCY, S2A_PRIORITY, S2A_GROUP_NAMES, S2A_GROUP_IDS
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"teams_count": 0,
|
||||
"config_updated": False,
|
||||
"message": ""
|
||||
}
|
||||
|
||||
errors = []
|
||||
|
||||
# 重载 config.toml
|
||||
try:
|
||||
new_cfg = _load_toml()
|
||||
if new_cfg:
|
||||
_cfg = new_cfg
|
||||
result["config_updated"] = True
|
||||
|
||||
# 更新关键配置变量
|
||||
EMAIL_PROVIDER = _cfg.get("email_provider", "kyx")
|
||||
|
||||
# 授权服务选择
|
||||
AUTH_PROVIDER = _cfg.get("auth_provider") or _cfg.get("gptmail", {}).get("auth_provider", "crs")
|
||||
|
||||
# Owner 入库开关
|
||||
_include_owners_top = _cfg.get("include_team_owners")
|
||||
_include_owners_gptmail = _cfg.get("gptmail", {}).get("include_team_owners")
|
||||
INCLUDE_TEAM_OWNERS = _include_owners_top if _include_owners_top is not None else (_include_owners_gptmail if _include_owners_gptmail is not None else False)
|
||||
|
||||
# 浏览器配置
|
||||
_browser = _cfg.get("browser", {})
|
||||
BROWSER_HEADLESS = _browser.get("headless", False)
|
||||
|
||||
# 账号配置
|
||||
_account = _cfg.get("account", {})
|
||||
ACCOUNTS_PER_TEAM = _account.get("accounts_per_team", 4)
|
||||
|
||||
# GPTMail 配置
|
||||
_gptmail = _cfg.get("gptmail", {})
|
||||
GPTMAIL_PREFIX = _gptmail.get("prefix", "")
|
||||
GPTMAIL_DOMAINS = _gptmail.get("domains", [])
|
||||
_gptmail_api_key = _gptmail.get("api_key", "")
|
||||
_gptmail_api_keys = _gptmail.get("api_keys", [])
|
||||
GPTMAIL_API_KEYS = _gptmail_api_keys if _gptmail_api_keys else ([_gptmail_api_key] if _gptmail_api_key else ["gpt-test"])
|
||||
|
||||
# 代理配置
|
||||
_proxy_enabled_top = _cfg.get("proxy_enabled")
|
||||
_proxy_enabled_browser = _cfg.get("browser", {}).get("proxy_enabled")
|
||||
PROXY_ENABLED = _proxy_enabled_top if _proxy_enabled_top is not None else (_proxy_enabled_browser if _proxy_enabled_browser is not None else False)
|
||||
|
||||
_proxies_top = _cfg.get("proxies")
|
||||
_proxies_browser = _cfg.get("browser", {}).get("proxies")
|
||||
PROXIES = (_proxies_top if _proxies_top is not None else (_proxies_browser if _proxies_browser is not None else [])) if PROXY_ENABLED else []
|
||||
|
||||
# S2A 配置
|
||||
_s2a = _cfg.get("s2a", {})
|
||||
S2A_API_BASE = _s2a.get("api_base", "")
|
||||
S2A_ADMIN_KEY = _s2a.get("admin_key", "")
|
||||
S2A_ADMIN_TOKEN = _s2a.get("admin_token", "")
|
||||
S2A_CONCURRENCY = _s2a.get("concurrency", 10)
|
||||
S2A_PRIORITY = _s2a.get("priority", 50)
|
||||
S2A_GROUP_NAMES = _s2a.get("group_names", [])
|
||||
S2A_GROUP_IDS = _s2a.get("group_ids", [])
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"config.toml: {e}")
|
||||
|
||||
# 重载 team.json
|
||||
try:
|
||||
new_raw_teams = _load_teams()
|
||||
_raw_teams = new_raw_teams
|
||||
|
||||
# 重新解析 TEAMS
|
||||
TEAMS.clear()
|
||||
for i, t in enumerate(_raw_teams):
|
||||
team_config = _parse_team_config(t, i)
|
||||
TEAMS.append(team_config)
|
||||
|
||||
result["teams_count"] = len(TEAMS)
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"team.json: {e}")
|
||||
|
||||
if errors:
|
||||
result["success"] = False
|
||||
result["message"] = "; ".join(errors)
|
||||
else:
|
||||
result["message"] = "配置重载成功"
|
||||
|
||||
return result
|
||||
|
||||
# 邮箱系统选择
|
||||
EMAIL_PROVIDER = _cfg.get("email_provider", "kyx") # "kyx" 或 "gptmail"
|
||||
|
||||
@@ -226,10 +330,89 @@ EMAIL_WEB_URL = _email.get("web_url", "")
|
||||
# GPTMail 临时邮箱配置
|
||||
_gptmail = _cfg.get("gptmail", {})
|
||||
GPTMAIL_API_BASE = _gptmail.get("api_base", "https://mail.chatgpt.org.uk")
|
||||
GPTMAIL_API_KEY = _gptmail.get("api_key", "gpt-test")
|
||||
GPTMAIL_PREFIX = _gptmail.get("prefix", "")
|
||||
GPTMAIL_DOMAINS = _gptmail.get("domains", [])
|
||||
|
||||
# GPTMail API Keys 支持多个 Key 轮询
|
||||
# 兼容旧配置: api_key (单个) 和新配置: api_keys (列表)
|
||||
_gptmail_api_key = _gptmail.get("api_key", "")
|
||||
_gptmail_api_keys = _gptmail.get("api_keys", [])
|
||||
GPTMAIL_API_KEYS = _gptmail_api_keys if _gptmail_api_keys else ([_gptmail_api_key] if _gptmail_api_key else ["gpt-test"])
|
||||
|
||||
# GPTMail Keys 文件 (用于动态管理)
|
||||
GPTMAIL_KEYS_FILE = BASE_DIR / "gptmail_keys.json"
|
||||
_gptmail_key_index = 0
|
||||
|
||||
|
||||
def _load_gptmail_keys() -> list:
|
||||
"""从文件加载 GPTMail API Keys"""
|
||||
if not GPTMAIL_KEYS_FILE.exists():
|
||||
return []
|
||||
try:
|
||||
with open(GPTMAIL_KEYS_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data.get("keys", [])
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _save_gptmail_keys(keys: list):
|
||||
"""保存 GPTMail API Keys 到文件"""
|
||||
try:
|
||||
with open(GPTMAIL_KEYS_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump({"keys": keys}, f, indent=2)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_gptmail_keys() -> list:
|
||||
"""获取所有 GPTMail API Keys (配置文件 + 动态添加)"""
|
||||
file_keys = _load_gptmail_keys()
|
||||
# 合并配置文件和动态添加的 Keys,去重
|
||||
all_keys = list(dict.fromkeys(GPTMAIL_API_KEYS + file_keys))
|
||||
return all_keys
|
||||
|
||||
|
||||
def add_gptmail_key(key: str) -> bool:
|
||||
"""添加 GPTMail API Key"""
|
||||
if not key or key.strip() == "":
|
||||
return False
|
||||
key = key.strip()
|
||||
current_keys = _load_gptmail_keys()
|
||||
if key in current_keys or key in GPTMAIL_API_KEYS:
|
||||
return False # 已存在
|
||||
current_keys.append(key)
|
||||
return _save_gptmail_keys(current_keys)
|
||||
|
||||
|
||||
def remove_gptmail_key(key: str) -> bool:
|
||||
"""删除 GPTMail API Key (仅限动态添加的)"""
|
||||
current_keys = _load_gptmail_keys()
|
||||
if key in current_keys:
|
||||
current_keys.remove(key)
|
||||
return _save_gptmail_keys(current_keys)
|
||||
return False
|
||||
|
||||
|
||||
def get_next_gptmail_key() -> str:
|
||||
"""轮询获取下一个 GPTMail API Key"""
|
||||
global _gptmail_key_index
|
||||
keys = get_gptmail_keys()
|
||||
if not keys:
|
||||
return "gpt-test"
|
||||
key = keys[_gptmail_key_index % len(keys)]
|
||||
_gptmail_key_index += 1
|
||||
return key
|
||||
|
||||
|
||||
def get_random_gptmail_key() -> str:
|
||||
"""随机获取一个 GPTMail API Key"""
|
||||
keys = get_gptmail_keys()
|
||||
if not keys:
|
||||
return "gpt-test"
|
||||
return random.choice(keys)
|
||||
|
||||
|
||||
def get_random_gptmail_domain() -> str:
|
||||
"""随机获取一个 GPTMail 可用域名 (排除黑名单)"""
|
||||
@@ -301,7 +484,10 @@ _domain_blacklist = _load_blacklist()
|
||||
AUTH_PROVIDER = _cfg.get("auth_provider") or _cfg.get("gptmail", {}).get("auth_provider", "crs")
|
||||
|
||||
# 是否将 Team Owner 也添加到授权服务
|
||||
INCLUDE_TEAM_OWNERS = _cfg.get("include_team_owners", False)
|
||||
# 注意: include_team_owners 可能在顶层或被误放在 gptmail section 下
|
||||
_include_owners_top = _cfg.get("include_team_owners")
|
||||
_include_owners_gptmail = _cfg.get("gptmail", {}).get("include_team_owners")
|
||||
INCLUDE_TEAM_OWNERS = _include_owners_top if _include_owners_top is not None else (_include_owners_gptmail if _include_owners_gptmail is not None else False)
|
||||
|
||||
# CRS
|
||||
_crs = _cfg.get("crs", {})
|
||||
@@ -377,8 +563,14 @@ TELEGRAM_CHECK_INTERVAL = _telegram.get("check_interval", 3600) # 默认1小时
|
||||
TELEGRAM_LOW_STOCK_THRESHOLD = _telegram.get("low_stock_threshold", 10) # 低库存阈值
|
||||
|
||||
# 代理
|
||||
PROXY_ENABLED = _cfg.get("proxy_enabled", False)
|
||||
PROXIES = _cfg.get("proxies", []) if PROXY_ENABLED else []
|
||||
# 注意: proxy_enabled 和 proxies 可能在顶层或被误放在 browser section 下
|
||||
_proxy_enabled_top = _cfg.get("proxy_enabled")
|
||||
_proxy_enabled_browser = _cfg.get("browser", {}).get("proxy_enabled")
|
||||
PROXY_ENABLED = _proxy_enabled_top if _proxy_enabled_top is not None else (_proxy_enabled_browser if _proxy_enabled_browser is not None else False)
|
||||
|
||||
_proxies_top = _cfg.get("proxies")
|
||||
_proxies_browser = _cfg.get("browser", {}).get("proxies")
|
||||
PROXIES = (_proxies_top if _proxies_top is not None else (_proxies_browser if _proxies_browser is not None else [])) if PROXY_ENABLED else []
|
||||
_proxy_index = 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user