import logging from typing import Optional 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 # ====================== # LOGGING # ====================== logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=Config.LOG_LEVEL ) logger = logging.getLogger(__name__) # ====================== # 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) # ====================== # CLIENT # ====================== vndb_client = VndbClient(use_sandbox=Config.USE_SANDBOX) # ====================== # HELPERS # ====================== HTML_HELP_HEADER = "VNDB Telegram Bot\n\n" # ====================== # HANDLERS # ====================== class BotHandlers: @staticmethod async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: text = """ Добро пожаловать в VNDB Telegram Бот! Этот бот позволяет искать визуальные новеллы, персонажей и релизы из VNDB. Команды: /search - поиск VN /char - персонажи /release - релизы /staff - сотрудники /producer - продюсеры /tag - теги /trait - черты /quote - цитаты /help - помощь """ await update.message.reply_text(text, parse_mode="HTML") # ====================== # HELP # ====================== @staticmethod async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: text = """ Справка по командам Поиск: /search <название> /char <имя> /release <название> /staff <имя> /producer <имя> Информация: /tag - популярные теги /trait - черты /quote <число> - цитаты /stats - статистика /schema - API Детальный просмотр: /vn_detail <id> /char_detail <id> /release_detail <id> Пример: /search Steins Gate /char Okabe /vn_detail v17 Ссылка: Примеры команд """ 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().lower()], # ✔️ без вложенности fields=["id", "title", "image{url}"], 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"][:3]: img = vn.get("image") if img and img.get("url"): try: await update.message.reply_photo( photo=f"https://t.vndb.org{img['url']}" ) except Exception as e: logger.warning(e) 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().lower()], # ✔️ важно fields=["id", "name", "image{url}"], results=10 ) text = "Персонажи:\n\n" for c in results.get("results", []): text += f"{c.get('id')} - {c.get('name')}\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().lower()], # ✔️ важно 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()