初始化

This commit is contained in:
van
2026-04-23 22:06:19 +08:00
commit e9823c2261
26 changed files with 840 additions and 0 deletions

153
tg_bridge/config.py Normal file
View File

@@ -0,0 +1,153 @@
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from dotenv import load_dotenv
# 优先从包上级目录wx_python 根)加载 .env避免工作目录不在项目根时失效
_ROOT = Path(__file__).resolve().parent.parent
load_dotenv(_ROOT / ".env")
load_dotenv()
def _parse_bot_usernames(raw: str) -> tuple[str, ...]:
"""从逗号分隔字符串解析 Bot 用户名(去 @、去空)。"""
parts = [p.strip().lstrip("@") for p in raw.split(",")]
bots = tuple(p for p in parts if p)
if not bots:
raise RuntimeError(
"缺少 TELEGRAM_BOT_USERNAME填写至少一个 Bot 用户名;多个用英文逗号分隔,不要带 @ 也可"
)
return bots
@dataclass(frozen=True)
class Settings:
api_id: int
api_hash: str
session_path: Path
"""已配置的 Bot 用户名列表(顺序:默认目标为第一个)。"""
bot_usernames: tuple[str, ...]
bridge_host: str
bridge_port: int
bridge_token: str | None
# Telethon 连接 Telegram 用的代理(与系统「全局代理」无关,需显式配置)
proxy_type: str | None
proxy_host: str | None
proxy_port: int | None
proxy_user: str | None
proxy_password: str | None
# SOCKS 上 rdns=True 会在代理侧解析域名,部分代理会卡死;默认 False 多为本机解析再连
proxy_rdns: bool
# Telethon 单次连接超时秒数(默认 10 经代理不够)
connect_timeout: int
connection_retries: int
retry_delay: int
# Telethon Connection 类tcp_obfuscated 在受限网络 + 代理下常比 tcp_full 更稳
connection_mode: str
# /v1/forward 在 wait_reply 时等待 Bot 回复的上限(秒)
bot_reply_timeout: float
# wait_reply 时默认取 Bot 第几条连续回复(可被请求体 reply_take_nth 覆盖)
bot_reply_take_nth: int
@property
def default_bot_username(self) -> str:
return self.bot_usernames[0]
def resolve_bot_username(self, bot: str | None) -> str:
"""将请求里的 bot 解析为已配置的用户名;省略则用列表第一个。"""
if bot is None:
return self.bot_usernames[0]
key = bot.strip().lstrip("@").lower()
by_lower = {b.lower(): b for b in self.bot_usernames}
if key not in by_lower:
raise ValueError(
f"未知 Bot「{bot}」,当前允许: {', '.join(self.bot_usernames)}"
)
return by_lower[key]
@staticmethod
def load() -> "Settings":
api_id_raw = os.environ.get("TELEGRAM_API_ID", "").strip()
api_hash = os.environ.get("TELEGRAM_API_HASH", "").strip()
if not api_id_raw or not api_hash:
raise RuntimeError(
"缺少 TELEGRAM_API_ID / TELEGRAM_API_HASH。"
"请到 https://my.telegram.org 申请后写入环境变量或 .env"
)
bot_raw = os.environ.get("TELEGRAM_BOT_USERNAME", "").strip()
bots = _parse_bot_usernames(bot_raw)
# 默认可写绝对路径避免「login 在项目目录、服务从别的工作目录启动」找不到同一份 session
_default_session = (_ROOT / "tg_bridge.session").resolve()
session_raw = os.environ.get("TELEGRAM_SESSION_PATH", "").strip()
session = (
Path(session_raw).expanduser().resolve()
if session_raw
else _default_session
)
token = os.environ.get("BRIDGE_TOKEN", "").strip() or None
host = os.environ.get("BRIDGE_HOST", "0.0.0.0").strip()
port = int(os.environ.get("BRIDGE_PORT", "18080"))
ptype = os.environ.get("TELEGRAM_PROXY_TYPE", "").strip() or None
phost = os.environ.get("TELEGRAM_PROXY_HOST", "").strip() or None
pport_raw = os.environ.get("TELEGRAM_PROXY_PORT", "").strip()
pport: int | None = int(pport_raw) if pport_raw else None
puser = os.environ.get("TELEGRAM_PROXY_USER", "").strip() or None
ppwd_raw = os.environ.get("TELEGRAM_PROXY_PASSWORD")
ppwd: str | None = None
if puser is not None:
ppwd = ppwd_raw if ppwd_raw is not None else ""
if ptype and (not phost or not pport):
raise RuntimeError(
"已设置 TELEGRAM_PROXY_TYPE 时,须同时设置 TELEGRAM_PROXY_HOST 与 TELEGRAM_PROXY_PORT"
)
if (phost or pport_raw) and not ptype:
raise RuntimeError("设置 TELEGRAM_PROXY_HOST / TELEGRAM_PROXY_PORT 时须设置 TELEGRAM_PROXY_TYPEhttp|socks5|socks4")
rdns_raw = os.environ.get("TELEGRAM_PROXY_RDNS", "").strip().lower()
if rdns_raw in ("1", "true", "yes", "on"):
proxy_rdns = True
elif rdns_raw in ("0", "false", "no", "off"):
proxy_rdns = False
elif not rdns_raw:
# 未配置HTTP 代理常用远程 DNSSOCKS 默认本机解析更稳
proxy_rdns = bool(ptype and str(ptype).lower() in ("http", "https"))
else:
raise RuntimeError("TELEGRAM_PROXY_RDNS 请使用 true/false")
connect_timeout = int(os.environ.get("TELEGRAM_CONNECT_TIMEOUT", "90"))
connection_retries = int(os.environ.get("TELEGRAM_CONNECTION_RETRIES", "5"))
retry_delay = int(os.environ.get("TELEGRAM_RETRY_DELAY", "3"))
connection_mode = os.environ.get("TELEGRAM_CONNECTION", "tcp_full").strip() or "tcp_full"
bot_reply_timeout = float(os.environ.get("TELEGRAM_BOT_REPLY_TIMEOUT", "120"))
bot_reply_take_nth = int(os.environ.get("BOT_REPLY_TAKE_NTH", "1"))
if bot_reply_take_nth < 1 or bot_reply_take_nth > 20:
raise RuntimeError("BOT_REPLY_TAKE_NTH 须在 120 之间")
return Settings(
api_id=int(api_id_raw),
api_hash=api_hash,
session_path=session,
bot_usernames=bots,
bridge_host=host,
bridge_port=port,
bridge_token=token,
proxy_type=ptype,
proxy_host=phost,
proxy_port=pport,
proxy_user=puser,
proxy_password=ppwd,
proxy_rdns=proxy_rdns,
connect_timeout=connect_timeout,
connection_retries=connection_retries,
retry_delay=retry_delay,
connection_mode=connection_mode,
bot_reply_timeout=bot_reply_timeout,
bot_reply_take_nth=bot_reply_take_nth,
)