diff --git a/bot.py b/bot.py
index c0f47ea..cc48f98 100644
--- a/bot.py
+++ b/bot.py
@@ -1,297 +1,125 @@
-import logging
-from typing import Optional
+import os
+import httpx
+from dotenv import load_dotenv
-from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
-from telegram.ext import (
- Application,
- CommandHandler,
- MessageHandler,
- CallbackQueryHandler,
- ConversationHandler,
- ContextTypes,
- filters,
-)
-
-from vndb_client import VndbClient
-from config import Config
-from utils import Formatter, ErrorHandler, QueryBuilder
-from detailed_handlers import get_detail_handlers
+load_dotenv()
-# ======================
-# LOGGING
-# ======================
-logging.basicConfig(
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
- level=Config.LOG_LEVEL
-)
-logger = logging.getLogger(__name__)
+class VndbClient:
+ """
+ Minimal safe VNDB client for Telegram bots
+ """
+ def __init__(self):
+ self.base_url = os.getenv("VNDB_API_URL", "https://api.vndb.org/kana")
+ self.token = os.getenv("VNDB_TOKEN")
-# ======================
-# STATES
-# ======================
-SEARCH_VN, SELECT_VN, VN_DETAILS = range(3)
-SEARCH_CHARACTER, SELECT_CHARACTER = range(2)
-SEARCH_RELEASE, SELECT_RELEASE = range(2)
-SEARCH_STAFF, SELECT_STAFF = range(2)
+ self.headers = {
+ "Content-Type": "application/json"
+ }
+ if self.token:
+ self.headers["Authorization"] = f"Token {self.token}"
-# ======================
-# CLIENT
-# ======================
-vndb_client = VndbClient(use_sandbox=Config.USE_SANDBOX)
+ # =========================
+ # CORE REQUEST
+ # =========================
+ async def _request(self, endpoint: str, payload: dict):
+ url = f"{self.base_url}{endpoint}"
+ async with httpx.AsyncClient(timeout=20) as client:
+ r = await client.post(url, json=payload, headers=self.headers)
-# ======================
-# HELPERS
-# ======================
-HTML_HELP_HEADER = "VNDB Telegram Bot\n\n"
+ # debug-friendly
+ if r.status_code >= 400:
+ print("VNDB ERROR:", r.status_code, r.text)
+ print("PAYLOAD:", payload)
+ r.raise_for_status()
+ return r.json()
-# ======================
-# HANDLERS
-# ======================
-class BotHandlers:
+ # =========================
+ # SAFE SEARCH WRAPPER
+ # =========================
+ def _safe_search(self, query: str):
+ if not query:
+ return None
+ return ["search", "=", query.strip()]
- @staticmethod
- async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- text = """
-Добро пожаловать в VNDB Telegram Бот!
+ # =========================
+ # VN SEARCH
+ # =========================
+ async def search_vn(self, query: str, limit: int = 10):
+ filters = self._safe_search(query)
+ if not filters:
+ return {"results": []}
-Этот бот позволяет искать визуальные новеллы, персонажей и релизы из VNDB.
+ return await self._request("/vn", {
+ "filters": filters,
+ "fields": "id,title,original,released,rating,votecount",
+ "results": limit
+ })
-Команды:
-/search - поиск VN
-/char - персонажи
-/release - релизы
-/staff - сотрудники
-/producer - продюсеры
-/tag - теги
-/trait - черты
-/quote - цитаты
-/help - помощь
- """
+ # =========================
+ # CHARACTER SEARCH
+ # =========================
+ async def search_character(self, query: str, limit: int = 10):
+ filters = self._safe_search(query)
+ if not filters:
+ return {"results": []}
- await update.message.reply_text(text, parse_mode="HTML")
+ return await self._request("/character", {
+ "filters": filters,
+ "fields": "id,name,original",
+ "results": limit
+ })
- # ======================
- # HELP
- # ======================
- @staticmethod
- async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- text = """
-Справка по командам
+ # =========================
+ # RELEASE SEARCH
+ # =========================
+ async def search_release(self, query: str, limit: int = 10):
+ filters = self._safe_search(query)
+ if not filters:
+ return {"results": []}
-Поиск:
-/search <название>
-/char <имя>
-/release <название>
-/staff <имя>
-/producer <имя>
+ return await self._request("/release", {
+ "filters": filters,
+ "fields": "id,title,original,released",
+ "results": limit
+ })
-Информация:
-/tag - популярные теги
-/trait - черты
-/quote <число> - цитаты
-/stats - статистика
-/schema - API
+ # =========================
+ # DETAIL VN
+ # =========================
+ async def vn_detail(self, vn_id: str):
+ return await self._request("/vn", {
+ "filters": ["id", "=", vn_id],
+ "fields": "id,title,original,released,rating,votecount,description,length,developer",
+ "results": 1
+ })
-Детальный просмотр:
-/vn_detail <id>
-/char_detail <id>
-/release_detail <id>
+ # =========================
+ # DETAIL CHARACTER
+ # =========================
+ async def character_detail(self, char_id: str):
+ return await self._request("/character", {
+ "filters": ["id", "=", char_id],
+ "fields": "id,name,original,gender,bloodtype",
+ "results": 1
+ })
-Пример:
-/search Steins Gate
-/char Okabe
-/vn_detail v17
+ # =========================
+ # DETAIL RELEASE
+ # =========================
+ async def release_detail(self, rel_id: str):
+ return await self._request("/release", {
+ "filters": ["id", "=", rel_id],
+ "fields": "id,title,original,released,platform,type,language,description",
+ "results": 1
+ })
-Ссылка:
-
-Примеры команд
-
- """
-
- await update.message.reply_text(text, parse_mode="HTML")
-
- # ======================
+ # =========================
# STATS
- # ======================
- @staticmethod
- async def stats(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- try:
- stats = await vndb_client.get_stats()
-
- text = f"""
-Статистика VNDB
-
-VN: {stats.get('vn', 0)}
-Characters: {stats.get('chars', 0)}
-Releases: {stats.get('releases', 0)}
-Producers: {stats.get('producers', 0)}
-Staff: {stats.get('staff', 0)}
-"""
-
- await update.message.reply_text(text, parse_mode="HTML")
-
- except Exception as e:
- logger.error(e)
- await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
-
- # ======================
- # SCHEMA
- # ======================
- @staticmethod
- async def schema(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- try:
- await update.message.reply_text("⏳ Загружаю схему...", parse_mode="HTML")
-
- schema = await vndb_client.get_schema()
-
- text = "API Schema VNDB\n\n"
-
- if "db_types" in schema:
- text += "Типы:\n"
- for k in list(schema["db_types"].keys())[:5]:
- text += f"• {k}\n"
-
- text += "\nДокументация API"
-
- await update.message.reply_text(text, parse_mode="HTML")
-
- except Exception as e:
- logger.error(e)
- await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
-
- # ======================
- # SEARCH VN
- # ======================
- @staticmethod
- async def search_vn(update: Update, context: ContextTypes.DEFAULT_TYPE):
- try:
- query = " ".join(context.args)
-
- if not query:
- await update.message.reply_text("Введите название")
- return ConversationHandler.END
-
- await update.message.reply_text(
- f"Поиск: {query}",
- parse_mode="HTML"
- )
-
- results = await vndb_client.query_vn(
- filters=["search", "=", query.strip()],
- fields=["id", "title"],
- results=10
- )
-
- if not results.get("results"):
- await update.message.reply_text("Ничего не найдено")
- return ConversationHandler.END
-
- text = "Результаты:\n\n"
-
- for vn in results["results"]:
- text += f"{vn.get('id')} - {vn.get('title')}\n"
-
- await update.message.reply_text(text, parse_mode="HTML")
-
- # images
- for vn in results["results"]:
- text += f"{vn.get('id')} - {vn.get('title')}\n"
-
- # 👇 ДОБАВЬ ССЫЛКУ НА VNDB
- text += f"https://vndb.org/{vn.get('id')}\n\n"
-
- except Exception as e:
- logger.error(e)
- await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
-
- return ConversationHandler.END
-
- # ======================
- # CHARACTER
- # ======================
- @staticmethod
- async def search_character(update: Update, context: ContextTypes.DEFAULT_TYPE):
- try:
- query = " ".join(context.args)
-
- if not query:
- await update.message.reply_text("Введите имя")
- return ConversationHandler.END
-
- results = await vndb_client.query_character(
- filters=["search", "=", query.strip()],
- fields=["id", "name", "original"],
- results=10
- )
-
- text = "Персонажи:\n\n"
-
- for c in results.get("results", []):
- text += f"{c.get('id')} - {c.get('name')} ({c.get('original', '')})\n"
-
- await update.message.reply_text(text, parse_mode="HTML")
-
- except Exception as e:
- logger.error(e)
- await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
-
- return ConversationHandler.END
-
- # ======================
- # RELEASE
- # ======================
- @staticmethod
- async def search_release(update: Update, context: ContextTypes.DEFAULT_TYPE):
- try:
- query = " ".join(context.args)
-
- results = await vndb_client.query_release(
- filters=["search", "=", query.strip()], # ✔️ важно
- fields=["id", "title"],
- results=10
- )
-
- text = "Релизы:\n\n"
-
- for r in results.get("results", []):
- text += f"{r.get('id')} - {r.get('title')}\n"
-
- await update.message.reply_text(text, parse_mode="HTML")
-
- except Exception as e:
- logger.error(e)
- await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
-
- return ConversationHandler.END
-
-
-# ======================
-# MAIN
-# ======================
-def main():
- Config.validate()
-
- app = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
-
- app.add_handler(CommandHandler("start", BotHandlers.start))
- app.add_handler(CommandHandler("help", BotHandlers.help_command))
- app.add_handler(CommandHandler("stats", BotHandlers.stats))
- app.add_handler(CommandHandler("schema", BotHandlers.schema))
- app.add_handler(CommandHandler("search", BotHandlers.search_vn))
- app.add_handler(CommandHandler("char", BotHandlers.search_character))
- app.add_handler(CommandHandler("release", BotHandlers.search_release))
-
- for h in get_detail_handlers():
- app.add_handler(h)
-
- logger.info("Bot started")
- app.run_polling()
-
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
+ # =========================
+ async def stats(self):
+ return await self._request("/stats", {})
\ No newline at end of file