mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
Initial import: vesti.nixg.ru — новостной апрув-проект (web, crawler, classifier, publisher, openspec)
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# Telegram Bot API клиент: httpx + SOCKS5-прокси.
|
||||
# TG из РФ заблокирован -> все запросы через TG_PROXY (socks5://127.0.0.1:1080).
|
||||
import httpx
|
||||
|
||||
from .config import settings
|
||||
|
||||
API = settings.api_base
|
||||
TIMEOUT = 20.0
|
||||
|
||||
|
||||
class TelegramError(Exception):
|
||||
"""Ошибка Bot API / сети. Код для HTTP-ответа сервиса."""
|
||||
|
||||
def __init__(self, message: str, http_code: int = 502, detail: str = ""):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.http_code = http_code # какой HTTP-статус вернуть вызывающему
|
||||
self.detail = detail
|
||||
|
||||
|
||||
def _client() -> httpx.Client:
|
||||
if not settings.bot_token:
|
||||
raise TelegramError("VESTI_BOT_TOKEN не задан", http_code=500, detail="missing token")
|
||||
return httpx.Client(proxy=settings.tg_proxy, timeout=TIMEOUT)
|
||||
|
||||
|
||||
def _url(method: str) -> str:
|
||||
return f"{API}/bot{settings.bot_token}/{method}"
|
||||
|
||||
|
||||
def _call(method: str, **params) -> dict:
|
||||
"""Выполняет метод Bot API. Возвращает result. Ошибки -> TelegramError."""
|
||||
try:
|
||||
with _client() as c:
|
||||
r = c.post(_url(method), json=params, timeout=TIMEOUT)
|
||||
except Exception as e:
|
||||
raise TelegramError(f"Сеть/прокси до Bot API: {e}", http_code=502) from e
|
||||
|
||||
if r.status_code != 200:
|
||||
# 401 bad token, 403 forbidden (бот не админ) и т.п.
|
||||
try:
|
||||
body = r.json()
|
||||
desc = body.get("description", "")
|
||||
except Exception:
|
||||
desc = r.text[:200]
|
||||
raise TelegramError(
|
||||
f"Bot API {method}: HTTP {r.status_code} {desc}",
|
||||
http_code=403 if r.status_code == 403 else 502,
|
||||
detail=desc,
|
||||
)
|
||||
data = r.json()
|
||||
if not data.get("ok"):
|
||||
desc = data.get("description", "")
|
||||
raise TelegramError(f"Bot API {method}: {desc}", http_code=502, detail=desc)
|
||||
return data.get("result", {})
|
||||
|
||||
|
||||
def send_message(chat_id: str, text: str, parse_mode: str | None = None) -> int:
|
||||
"""Отправляет текст. По умолчанию — plain (карточка репоста: исходный текст поста
|
||||
с сырыми символами <,>,&; ссылки Telegram распознаёт сам).
|
||||
link_preview_options={is_disabled:True} — НЕ показывать предпросмотр ссылки
|
||||
(чтобы канал не выглядел как агрегатор ссылок с превью)."""
|
||||
params = {
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
"link_preview_options": {"is_disabled": True},
|
||||
}
|
||||
if parse_mode:
|
||||
params["parse_mode"] = parse_mode
|
||||
return _call("sendMessage", **params)["message_id"]
|
||||
|
||||
|
||||
def send_photo(chat_id: str, photo_path: str, caption: str = "") -> int:
|
||||
"""Отправляет фото (медиа первым). Возвращает message_id."""
|
||||
if not photo_path:
|
||||
raise TelegramError("photo_path пуст", http_code=400)
|
||||
try:
|
||||
with _client() as c, open(photo_path, "rb") as f:
|
||||
r = c.post(
|
||||
_url("sendPhoto"),
|
||||
data={"chat_id": chat_id, "caption": caption[:1024]},
|
||||
files={"photo": f},
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
except TelegramError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise TelegramError(f"sendPhoto сеть: {e}", http_code=502) from e
|
||||
|
||||
if r.status_code != 200:
|
||||
try:
|
||||
desc = r.json().get("description", "")
|
||||
except Exception:
|
||||
desc = r.text[:200]
|
||||
raise TelegramError(f"sendPhoto: HTTP {r.status_code} {desc}", http_code=403 if r.status_code == 403 else 502)
|
||||
data = r.json()
|
||||
if not data.get("ok"):
|
||||
raise TelegramError(f"sendPhoto: {data.get('description','')}", http_code=502)
|
||||
return data["result"]["message_id"]
|
||||
|
||||
|
||||
def send_video(chat_id: str, video_path: str, caption: str = "") -> int:
|
||||
"""Отправляет видео (медиа первым). Возвращает message_id."""
|
||||
if not video_path:
|
||||
raise TelegramError("video_path пуст", http_code=400)
|
||||
try:
|
||||
with _client() as c, open(video_path, "rb") as f:
|
||||
r = c.post(
|
||||
_url("sendVideo"),
|
||||
data={"chat_id": chat_id, "caption": caption[:1024]},
|
||||
files={"video": f},
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
except TelegramError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise TelegramError(f"sendVideo сеть: {e}", http_code=502) from e
|
||||
|
||||
if r.status_code != 200:
|
||||
try:
|
||||
desc = r.json().get("description", "")
|
||||
except Exception:
|
||||
desc = r.text[:200]
|
||||
raise TelegramError(f"sendVideo: HTTP {r.status_code} {desc}", http_code=403 if r.status_code == 403 else 502)
|
||||
data = r.json()
|
||||
if not data.get("ok"):
|
||||
raise TelegramError(f"sendVideo: {data.get('description','')}", http_code=502)
|
||||
return data["result"]["message_id"]
|
||||
|
||||
|
||||
def send_media(chat_id: str, media_path: str, caption: str = "") -> int:
|
||||
"""Отправляет медиа по расширению: фото или видео. Возвращает message_id."""
|
||||
if str(media_path).lower().endswith((".mp4", ".mov", ".avi", ".mkv")):
|
||||
return send_video(chat_id, media_path, caption)
|
||||
return send_photo(chat_id, media_path, caption)
|
||||
|
||||
|
||||
def get_views(chat_id: str, message_id: int) -> int:
|
||||
"""Просмотры опубликованного сообщения (getMessage → views)."""
|
||||
try:
|
||||
r = _call("getMessage", chat_id=chat_id, message_id=message_id)
|
||||
return int(r.get("views") or r.get("forwards") or 0)
|
||||
except TelegramError:
|
||||
return 0
|
||||
|
||||
|
||||
def get_me() -> dict:
|
||||
return _call("getMe")
|
||||
|
||||
|
||||
def delete_message(chat_id: str, message_id: int) -> bool:
|
||||
"""Удаляет сообщение из канала. True если успешно."""
|
||||
try:
|
||||
_call("deleteMessage", chat_id=chat_id, message_id=message_id)
|
||||
return True
|
||||
except TelegramError:
|
||||
return False
|
||||
|
||||
|
||||
def get_chat(chat_id: str) -> dict:
|
||||
return _call("getChat", chat_id=chat_id)
|
||||
Reference in New Issue
Block a user