fix: use telegram proxy pool in web api

This commit is contained in:
ngfchl
2026-07-07 14:24:52 +08:00
parent b40e2a2dc8
commit 290f5f0dbe
2 changed files with 47 additions and 20 deletions
+1
View File
@@ -18,6 +18,7 @@ OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "qwen3:8b")
# Proxy
PROXY_URL = os.getenv("PROXY_URL", "http://192.168.123.10:11055")
TG_PROXY_URL = os.getenv("TG_PROXY_URL", PROXY_URL)
TG_PROXY_URLS = [x.strip() for x in os.getenv("TG_PROXY_URLS", TG_PROXY_URL).split(",") if x.strip()]
# Web
WEB_PORT = int(os.getenv("WEB_PORT", "8766"))
+46 -20
View File
@@ -245,6 +245,47 @@ async def api_stats(day: str = Query(default=None, pattern=r"^\d{4}-\d{2}-\d{2}$
return JSONResponse(await get_daily_stats(day, chat_id=chat_id))
def tg_proxy_urls(config) -> list[str | None]:
urls: list[str | None] = []
for value in getattr(config, "TG_PROXY_URLS", []) or []:
if value and value not in urls:
urls.append(value)
for value in [getattr(config, "TG_PROXY_URL", None), getattr(config, "PROXY_URL", None)]:
if value and value not in urls:
urls.append(value)
return urls or [None]
async def run_tg_request(operation, *, connect_timeout: float = 20.0, read_timeout: float = 20.0, label: str = "tg"):
import config
from telegram import Bot
from telegram.error import BadRequest, Forbidden, NetworkError, TimedOut
from telegram.request import HTTPXRequest
last_error: Exception | None = None
proxies = tg_proxy_urls(config)
for idx, proxy in enumerate(proxies, 1):
try:
bot = Bot(
token=config.TG_BOT_TOKEN,
request=HTTPXRequest(proxy=proxy, connect_timeout=connect_timeout, read_timeout=read_timeout),
)
return await operation(bot)
except (BadRequest, Forbidden):
raise
except (NetworkError, TimedOut, Exception) as e:
last_error = e
if idx < len(proxies):
logging.getLogger("spam_guard").warning(
f"🌐 Web TG 请求失败,切换代理重试: {label}: {type(e).__name__}: {e}; next={proxies[idx]}"
)
continue
raise
if last_error:
raise last_error
raise RuntimeError("TG 请求失败")
def parse_draw_at(value):
if not value:
return None
@@ -307,9 +348,6 @@ async def api_actions(limit: int = Query(default=100, ge=1, le=500)):
@app.get("/api/groups")
async def api_groups():
import config
from telegram import Bot
from telegram.request import HTTPXRequest
sources = []
for model in (Message, Ban, Action, GameScore, LotteryEvent, ShopItem):
try:
@@ -320,12 +358,11 @@ async def api_groups():
if config.TG_CHAT_ID and config.TG_CHAT_ID not in ids:
ids.insert(0, config.TG_CHAT_ID)
bot = Bot(token=config.TG_BOT_TOKEN, request=HTTPXRequest(proxy=getattr(config, "TG_PROXY_URL", config.PROXY_URL), connect_timeout=10.0, read_timeout=10.0))
result = []
for chat_id in ids:
name = str(chat_id)
try:
chat = await bot.get_chat(chat_id=chat_id)
chat = await run_tg_request(lambda bot, cid=chat_id: bot.get_chat(chat_id=cid), connect_timeout=10.0, read_timeout=10.0, label=f"get_chat:{chat_id}")
name = chat.title or chat.full_name or chat.username or str(chat_id)
except Exception:
pass
@@ -486,9 +523,6 @@ async def api_restart():
@app.post("/api/send")
async def api_send(request: Request):
import config
from telegram import Bot
from telegram.request import HTTPXRequest
data = await request.json()
text = str(data.get("text", "")).strip()
if not text:
@@ -497,8 +531,7 @@ async def api_send(request: Request):
return JSONResponse({"ok": False, "error": "消息太长"}, status_code=400)
chat_id = int(data.get("chat_id") or config.TG_CHAT_ID)
bot = Bot(token=config.TG_BOT_TOKEN, request=HTTPXRequest(proxy=getattr(config, "TG_PROXY_URL", config.PROXY_URL)))
msg = await bot.send_message(chat_id=chat_id, text=text)
msg = await run_tg_request(lambda bot: bot.send_message(chat_id=chat_id, text=text), label="send_message")
rec = await Message.create(
msg_id=msg.message_id,
chat_id=chat_id,
@@ -527,9 +560,7 @@ async def api_send(request: Request):
async def api_delete_message(message_id: int, request: Request):
"""手动删除聊天记录:先删 Telegram 消息,再把数据库记录标记为已删除。"""
import config
from telegram import Bot
from telegram.error import BadRequest
from telegram.request import HTTPXRequest
data = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
ban_user = bool(data.get("ban_user") or data.get("ban"))
@@ -542,11 +573,10 @@ async def api_delete_message(message_id: int, request: Request):
if not rec.chat_id or not rec.msg_id:
return JSONResponse({"ok": False, "error": "缺少 TG chat_id/msg_id,无法删除 TG 消息"}, status_code=400)
bot = Bot(token=config.TG_BOT_TOKEN, request=HTTPXRequest(proxy=getattr(config, "TG_PROXY_URL", config.PROXY_URL), connect_timeout=20.0, read_timeout=20.0))
tg_deleted = True
tg_error = None
try:
await bot.delete_message(chat_id=rec.chat_id, message_id=rec.msg_id)
await run_tg_request(lambda bot: bot.delete_message(chat_id=rec.chat_id, message_id=rec.msg_id), connect_timeout=20.0, read_timeout=20.0, label="delete_message")
except BadRequest as e:
# 消息已不存在时,允许继续标记数据库;其他 TG 删除失败不标记。
if "message to delete not found" in str(e).lower():
@@ -566,7 +596,7 @@ async def api_delete_message(message_id: int, request: Request):
try:
from datetime import timedelta
unban_at = now_tz() + timedelta(minutes=config.BAN_DURATION_MINUTES)
await bot.ban_chat_member(chat_id=rec.chat_id, user_id=rec.user_id, until_date=int(unban_at.timestamp()))
await run_tg_request(lambda bot: bot.ban_chat_member(chat_id=rec.chat_id, user_id=rec.user_id, until_date=int(unban_at.timestamp())), connect_timeout=20.0, read_timeout=20.0, label="ban_chat_member")
ban = await Ban.create(
user_id=rec.user_id,
username=rec.username,
@@ -618,11 +648,7 @@ async def api_scores(limit: int = Query(default=50, ge=1, le=200)):
async def api_unban(user_id: int):
import config
try:
from telegram import Bot
from telegram.request import HTTPXRequest
bot = Bot(token=config.TG_BOT_TOKEN, request=HTTPXRequest(proxy=getattr(config, "TG_PROXY_URL", config.PROXY_URL)))
await bot.unban_chat_member(chat_id=config.TG_CHAT_ID, user_id=user_id)
await run_tg_request(lambda bot: bot.unban_chat_member(chat_id=config.TG_CHAT_ID, user_id=user_id), label="unban_chat_member")
await Ban.filter(user_id=user_id).update(auto_unban=False, unban_at=now_tz().replace(tzinfo=None))
await Action.create(action="UNBAN", user_id=user_id, details={"method": "web"})
await broadcast_sse("unban", {"user_id": user_id})