Улучшение структуры кода и форматирования сообщений бота VNDB
This commit is contained in:
817
bot.py
817
bot.py
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Application,
|
Application,
|
||||||
@@ -10,687 +11,291 @@ from telegram.ext import (
|
|||||||
ContextTypes,
|
ContextTypes,
|
||||||
filters,
|
filters,
|
||||||
)
|
)
|
||||||
|
|
||||||
from vndb_client import VndbClient
|
from vndb_client import VndbClient
|
||||||
from config import Config
|
from config import Config
|
||||||
from utils import Formatter, ErrorHandler, QueryBuilder
|
from utils import Formatter, ErrorHandler, QueryBuilder
|
||||||
from detailed_handlers import get_detail_handlers
|
from detailed_handlers import get_detail_handlers
|
||||||
|
|
||||||
# Setup logging
|
|
||||||
|
# ======================
|
||||||
|
# LOGGING
|
||||||
|
# ======================
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
level=Config.LOG_LEVEL
|
level=Config.LOG_LEVEL
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# States for conversation handlers
|
|
||||||
|
# ======================
|
||||||
|
# STATES
|
||||||
|
# ======================
|
||||||
SEARCH_VN, SELECT_VN, VN_DETAILS = range(3)
|
SEARCH_VN, SELECT_VN, VN_DETAILS = range(3)
|
||||||
SEARCH_CHARACTER, SELECT_CHARACTER = range(2)
|
SEARCH_CHARACTER, SELECT_CHARACTER = range(2)
|
||||||
SEARCH_RELEASE, SELECT_RELEASE = range(2)
|
SEARCH_RELEASE, SELECT_RELEASE = range(2)
|
||||||
SEARCH_STAFF, SELECT_STAFF = range(2)
|
SEARCH_STAFF, SELECT_STAFF = range(2)
|
||||||
|
|
||||||
# Global VNDB client
|
|
||||||
|
# ======================
|
||||||
|
# CLIENT
|
||||||
|
# ======================
|
||||||
vndb_client = VndbClient(use_sandbox=Config.USE_SANDBOX)
|
vndb_client = VndbClient(use_sandbox=Config.USE_SANDBOX)
|
||||||
|
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# HELPERS
|
||||||
|
# ======================
|
||||||
|
HTML_HELP_HEADER = "<b>VNDB Telegram Bot</b>\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# HANDLERS
|
||||||
|
# ======================
|
||||||
class BotHandlers:
|
class BotHandlers:
|
||||||
"""Telegram bot command and message handlers"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""Start command handler"""
|
text = """
|
||||||
welcome_text = """
|
<b>Добро пожаловать в VNDB Telegram Бот!</b>
|
||||||
\*\*Добро пожаловать в VNDB Telegram Бот\!\*\*
|
|
||||||
|
|
||||||
Этот бот позволяет искать информацию о визуальных новеллах, персонажах, релизах и многом другом из базы данных VNDB\.
|
Этот бот позволяет искать визуальные новеллы, персонажей и релизы из VNDB.
|
||||||
|
|
||||||
\*\*Доступные команды:\*\*
|
<b>Команды:</b>
|
||||||
/search \- Поиск визуальных новелл
|
/search - поиск VN
|
||||||
/char \- Поиск персонажей
|
/char - персонажи
|
||||||
/release \- Поиск релизов
|
/release - релизы
|
||||||
/staff \- Поиск сотрудников
|
/staff - сотрудники
|
||||||
/producer \- Поиск продюсеров
|
/producer - продюсеры
|
||||||
/tag \- Поиск тегов
|
/tag - теги
|
||||||
/trait \- Поиск черт характера
|
/trait - черты
|
||||||
/quote \- Поиск цитат
|
/quote - цитаты
|
||||||
/stats \- Статистика базы данных
|
/help - помощь
|
||||||
/schema \- Информация о схеме API
|
|
||||||
/help \- Справка по командам
|
|
||||||
|
|
||||||
Используйте /help для получения подробной информации
|
|
||||||
Также можете ознакомится с примерами команд по ссылке: https://git\.kotac\.ru/King\-of\-the\-all\-Cookies/ayako/src/branch/main/EXAMPLES\.md
|
|
||||||
"""
|
"""
|
||||||
await update.message.reply_text(welcome_text, parse_mode="Markdown")
|
|
||||||
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# HELP
|
||||||
|
# ======================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""Help command handler"""
|
text = """
|
||||||
help_text = """
|
<b>Справка по командам</b>
|
||||||
\*\*Справка по командам VNDB Бота\*\*
|
|
||||||
|
|
||||||
\*\*Поиск информации:\*\*
|
<b>Поиск:</b>
|
||||||
/search <название\> \- Поиск визуальных новелл по названию
|
/search <название>
|
||||||
/char <название\> \- Поиск персонажей по имени
|
/char <имя>
|
||||||
/release <название\> \- Поиск релизов
|
/release <название>
|
||||||
/staff <название\> \- Поиск сотрудников \(сценаристы, художники и т\.д\.\)
|
/staff <имя>
|
||||||
/producer <название\> \- Поиск продюсеров
|
/producer <имя>
|
||||||
/tag \- Список популярных тегов
|
|
||||||
/trait \- Список черт характера
|
|
||||||
/quote <количество\> \- Получить случайные цитаты
|
|
||||||
|
|
||||||
\*\*Подробный просмотр \(с картинками\):\*\*
|
<b>Информация:</b>
|
||||||
/vn\_detail <ID\> \- Просмотр полной информации о ВН с обложкой
|
/tag - популярные теги
|
||||||
\_\_Пример: /vn\_detail v17\_\_
|
/trait - черты
|
||||||
/char\_detail <ID\> \- Просмотр информации о персонаже с аватаром
|
/quote <число> - цитаты
|
||||||
\_\_Пример: /char\_detail c1\_\_
|
/stats - статистика
|
||||||
/release\_detail <ID\> \- Просмотр информации о релизе с картинкой
|
/schema - API
|
||||||
\_\_Пример: /release\_detail r1\_\_
|
|
||||||
|
|
||||||
\*\*Информация:\*\*
|
<b>Детальный просмотр:</b>
|
||||||
/stats \- Показать статистику базы данных VNDB
|
/vn_detail <id>
|
||||||
/schema \- Получить информацию о доступных полях API
|
/char_detail <id>
|
||||||
/authinfo \- Информация об авторизации \(если настроена\)
|
/release_detail <id>
|
||||||
|
|
||||||
\*\*Функции пользователя \(требуют токена\):\*\*
|
<b>Пример:</b>
|
||||||
Чтобы использовать функции списка, установите токен в переменной окружения VNDB\_TOKEN
|
|
||||||
|
|
||||||
\*\*Примеры использования:\*\*
|
|
||||||
/search Steins Gate
|
/search Steins Gate
|
||||||
/char Okabe
|
/char Okabe
|
||||||
/release Windows
|
/vn_detail v17
|
||||||
/vn\_detail v17
|
|
||||||
/char\_detail c25
|
|
||||||
/stats
|
|
||||||
|
|
||||||
\*\*Важно:\*\*
|
<b>Ссылка:</b>
|
||||||
\- Бот работает в асинхронном режиме
|
<a href="https://git.kotac.ru/King-of-the-all-Cookies/ayako/src/branch/main/EXAMPLES.md">
|
||||||
\- Результаты ограничены 10 элементами по умолчанию
|
Примеры команд
|
||||||
\- При поиске автоматически отправляются картинки \(первые 3 результата\)
|
</a>
|
||||||
\- Для просмотра полной информации с картинкой используйте /vn\_detail, /char\_detail и т\.д\.
|
|
||||||
"""
|
"""
|
||||||
await update.message.reply_text(help_text, parse_mode="Markdown")
|
|
||||||
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# STATS
|
||||||
|
# ======================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def stats(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def stats(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""Get database statistics"""
|
|
||||||
try:
|
try:
|
||||||
stats = await vndb_client.get_stats()
|
stats = await vndb_client.get_stats()
|
||||||
|
|
||||||
stats_text = f"""
|
|
||||||
\*\*Статистика базы данных VNDB:\*\*
|
|
||||||
|
|
||||||
Визуальные новеллы: {stats.get('vn', 0):,}
|
text = f"""
|
||||||
Персонажи: {stats.get('chars', 0):,}
|
<b>Статистика VNDB</b>
|
||||||
Релизы: {stats.get('releases', 0):,}
|
|
||||||
Продюсеры: {stats.get('producers', 0):,}
|
VN: {stats.get('vn', 0)}
|
||||||
Сотрудники: {stats.get('staff', 0):,}
|
Characters: {stats.get('chars', 0)}
|
||||||
Теги: {stats.get('tags', 0):,}
|
Releases: {stats.get('releases', 0)}
|
||||||
Черты характера: {stats.get('traits', 0):,}
|
Producers: {stats.get('producers', 0)}
|
||||||
"""
|
Staff: {stats.get('staff', 0)}
|
||||||
await update.message.reply_text(stats_text, parse_mode="Markdown")
|
"""
|
||||||
|
|
||||||
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting stats: {e}")
|
logger.error(e)
|
||||||
error_msg = ErrorHandler.format_error(e)
|
await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
|
||||||
await update.message.reply_text(f"❌ {error_msg}")
|
|
||||||
|
# ======================
|
||||||
|
# SCHEMA
|
||||||
|
# ======================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def schema(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def schema(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""Get API schema information"""
|
|
||||||
try:
|
try:
|
||||||
await update.message.reply_text(
|
await update.message.reply_text("⏳ Загружаю схему...", parse_mode="HTML")
|
||||||
"⏳ Загружаю информацию о схеме... (это может занять некоторое время)",
|
|
||||||
parse_mode="Markdown"
|
|
||||||
)
|
|
||||||
|
|
||||||
schema = await vndb_client.get_schema()
|
schema = await vndb_client.get_schema()
|
||||||
|
|
||||||
# Build schema info
|
text = "<b>API Schema VNDB</b>\n\n"
|
||||||
schema_text = "**Информация о схеме VNDB API:**\n\n"
|
|
||||||
|
|
||||||
# Database types
|
|
||||||
if "db_types" in schema:
|
if "db_types" in schema:
|
||||||
schema_text += "**Типы данных:**\n"
|
text += "<b>Типы:</b>\n"
|
||||||
for db_type, info in list(schema["db_types"].items())[:5]:
|
for k in list(schema["db_types"].keys())[:5]:
|
||||||
schema_text += f"• {db_type}\n"
|
text += f"• {k}\n"
|
||||||
schema_text += "\n"
|
|
||||||
|
text += "\n<a href='https://api.vndb.org/kana'>Документация API</a>"
|
||||||
# Search fields
|
|
||||||
if "fields" in schema:
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
schema_text += "**Доступные поля для запросов:**\n"
|
|
||||||
for field_type, fields in list(schema["fields"].items())[:3]:
|
|
||||||
schema_text += f"• {field_type}\n"
|
|
||||||
schema_text += "\n"
|
|
||||||
|
|
||||||
schema_text += "**Для полного списка полей и типов посетите: https://api.vndb.org/kana**"
|
|
||||||
|
|
||||||
await update.message.reply_text(schema_text, parse_mode="Markdown")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting schema: {e}")
|
logger.error(e)
|
||||||
error_msg = ErrorHandler.format_error(e)
|
await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
|
||||||
await update.message.reply_text(f"❌ {error_msg}")
|
|
||||||
|
# ======================
|
||||||
|
# SEARCH VN
|
||||||
|
# ======================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def search_vn(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
async def search_vn(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Search for visual novels"""
|
|
||||||
try:
|
try:
|
||||||
args = " ".join(context.args) if context.args else ""
|
query = " ".join(context.args)
|
||||||
|
|
||||||
if not args:
|
if not query:
|
||||||
await update.message.reply_text(
|
await update.message.reply_text("Введите название")
|
||||||
"Пожалуйста, укажите название для поиска\n"
|
|
||||||
"Пример: /search Steins Gate"
|
|
||||||
)
|
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
await update.message.reply_text(f"Поиск визуальных новелл: **{args}**\nЗагрузка...", parse_mode="Markdown")
|
await update.message.reply_text(
|
||||||
|
f"<b>Поиск:</b> {query}",
|
||||||
# Search for VN
|
parse_mode="HTML"
|
||||||
filters = ["search", "=", args]
|
)
|
||||||
|
|
||||||
results = await vndb_client.query_vn(
|
results = await vndb_client.query_vn(
|
||||||
filters=[filters],
|
filters=["search", "=", query],
|
||||||
fields=["title", "original", "released", "rating", "votecount", "image{url}"],
|
fields=["title", "id", "image{url}"],
|
||||||
results=10
|
results=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if not results.get("results"):
|
if not results.get("results"):
|
||||||
await update.message.reply_text("Ничего не найдено")
|
await update.message.reply_text("Ничего не найдено")
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
# Format results
|
text = "<b>Результаты:</b>\n\n"
|
||||||
response_text = f"**Результаты поиска: {args}**\n\n"
|
|
||||||
|
for vn in results["results"]:
|
||||||
for i, vn in enumerate(results["results"], 1):
|
text += f"{vn.get('id')} - {vn.get('title')}\n"
|
||||||
vn_id = vn.get("id", "Unknown")
|
|
||||||
title = vn.get("title", "Unknown")
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
original = vn.get("original", "")
|
|
||||||
released = vn.get("released", "Unknown")
|
# images
|
||||||
rating = vn.get("rating", 0)
|
for vn in results["results"][:3]:
|
||||||
votecount = vn.get("votecount", 0)
|
img = vn.get("image")
|
||||||
|
if img and img.get("url"):
|
||||||
response_text += (
|
try:
|
||||||
f"{i}. **{title}**\n"
|
await update.message.reply_photo(
|
||||||
f" ID: {vn_id}\n"
|
photo=f"https://t.vndb.org{img['url']}"
|
||||||
)
|
)
|
||||||
if original:
|
except Exception as e:
|
||||||
response_text += f" Оригинал: {original}\n"
|
logger.warning(e)
|
||||||
response_text += (
|
|
||||||
f" Релиз: {released}\n"
|
|
||||||
f" Рейтинг: {rating/10:.1f}/10 ({votecount} голосов)\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
response_text += f"\nВсего найдено: {len(results['results'])} результатов"
|
|
||||||
if results.get("more"):
|
|
||||||
response_text += " (есть еще результаты)"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
# Send images if available
|
|
||||||
for vn in results["results"][:3]: # Send images for first 3 results
|
|
||||||
image = vn.get("image")
|
|
||||||
if image and isinstance(image, dict):
|
|
||||||
image_url = image.get("url")
|
|
||||||
if image_url:
|
|
||||||
try:
|
|
||||||
title = vn.get("title", "VN")
|
|
||||||
await update.message.reply_photo(
|
|
||||||
photo=f"https://t.vndb.org{image_url}",
|
|
||||||
caption=f"{title}",
|
|
||||||
parse_mode="Markdown"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Could not send image: {e}")
|
|
||||||
|
|
||||||
# Store results for detail view
|
|
||||||
context.user_data["vn_results"] = results["results"]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching VN: {e}")
|
logger.error(e)
|
||||||
error_msg = ErrorHandler.format_error(e)
|
await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# CHARACTER
|
||||||
|
# ======================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def search_character(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
async def search_character(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Search for characters"""
|
|
||||||
try:
|
try:
|
||||||
args = " ".join(context.args) if context.args else ""
|
query = " ".join(context.args)
|
||||||
|
|
||||||
if not args:
|
if not query:
|
||||||
await update.message.reply_text(
|
await update.message.reply_text("Введите имя")
|
||||||
"Пожалуйста, укажите имя персонажа\n"
|
|
||||||
"Пример: /char Okabe"
|
|
||||||
)
|
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
await update.message.reply_text(f"Поиск персонажей: **{args}**\n⏳ Загрузка...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
filters = ["search", "=", args]
|
|
||||||
results = await vndb_client.query_character(
|
results = await vndb_client.query_character(
|
||||||
filters=[filters],
|
filters=["search", "=", query],
|
||||||
fields=["name", "original", "gender", "vn", "image{url}"],
|
fields=["name", "id", "image{url}"],
|
||||||
results=10
|
results=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if not results.get("results"):
|
text = "<b>Персонажи:</b>\n\n"
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return ConversationHandler.END
|
for c in results.get("results", []):
|
||||||
|
text += f"{c.get('id')} - {c.get('name')}\n"
|
||||||
response_text = f"**Результаты поиска персонажей: {args}**\n\n"
|
|
||||||
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
for i, char in enumerate(results["results"], 1):
|
|
||||||
char_id = char.get("id", "Unknown")
|
|
||||||
name = char.get("name", "Unknown")
|
|
||||||
original = char.get("original", "")
|
|
||||||
gender = char.get("gender", "Unknown")
|
|
||||||
vns = char.get("vn", [])
|
|
||||||
|
|
||||||
response_text += (
|
|
||||||
f"{i}. **{name}**\n"
|
|
||||||
f" ID: {char_id}\n"
|
|
||||||
)
|
|
||||||
if original:
|
|
||||||
response_text += f" Оригинал: {original}\n"
|
|
||||||
response_text += f" Пол: {gender}\n"
|
|
||||||
if vns:
|
|
||||||
response_text += f" Появляется в {len(vns)} VN\n\n"
|
|
||||||
else:
|
|
||||||
response_text += "\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
# Send character images if available
|
|
||||||
for char in results["results"][:3]: # Send images for first 3 results
|
|
||||||
image = char.get("image")
|
|
||||||
if image and isinstance(image, dict):
|
|
||||||
image_url = image.get("url")
|
|
||||||
if image_url:
|
|
||||||
try:
|
|
||||||
name = char.get("name", "Character")
|
|
||||||
await update.message.reply_photo(
|
|
||||||
photo=f"https://t.vndb.org{image_url}",
|
|
||||||
caption=f"{name}",
|
|
||||||
parse_mode="Markdown"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Could not send character image: {e}")
|
|
||||||
|
|
||||||
context.user_data["char_results"] = results["results"]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching characters: {e}")
|
logger.error(e)
|
||||||
error_msg = ErrorHandler.format_error(e)
|
await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# RELEASE
|
||||||
|
# ======================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def search_release(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
async def search_release(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Search for releases"""
|
|
||||||
try:
|
try:
|
||||||
args = " ".join(context.args) if context.args else ""
|
query = " ".join(context.args)
|
||||||
|
|
||||||
if not args:
|
|
||||||
await update.message.reply_text(
|
|
||||||
"Пожалуйста, укажите название для поиска\n"
|
|
||||||
"Пример: /release Windows"
|
|
||||||
)
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
await update.message.reply_text(f"Поиск релизов: **{args}**\nЗагрузка...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
filters = ["search", "=", args]
|
|
||||||
results = await vndb_client.query_release(
|
results = await vndb_client.query_release(
|
||||||
filters=[filters],
|
filters=["search", "=", query],
|
||||||
fields=["title", "original", "released", "platform", "type", "image{url}"],
|
fields=["title", "id"],
|
||||||
results=10
|
results=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if not results.get("results"):
|
text = "<b>Релизы:</b>\n\n"
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return ConversationHandler.END
|
for r in results.get("results", []):
|
||||||
|
text += f"{r.get('id')} - {r.get('title')}\n"
|
||||||
response_text = f"**Результаты поиска релизов: {args}**\n\n"
|
|
||||||
|
await update.message.reply_text(text, parse_mode="HTML")
|
||||||
for i, release in enumerate(results["results"], 1):
|
|
||||||
release_id = release.get("id", "Unknown")
|
|
||||||
title = release.get("title", "Unknown")
|
|
||||||
original = release.get("original", "")
|
|
||||||
released = release.get("released", "Unknown")
|
|
||||||
platform = release.get("platform", "Unknown")
|
|
||||||
release_type = release.get("type", "Unknown")
|
|
||||||
|
|
||||||
response_text += (
|
|
||||||
f"{i}. **{title}**\n"
|
|
||||||
f" ID: {release_id}\n"
|
|
||||||
)
|
|
||||||
if original:
|
|
||||||
response_text += f" Оригинал: {original}\n"
|
|
||||||
response_text += (
|
|
||||||
f" Дата: {released}\n"
|
|
||||||
f" Платформа: {platform}\n"
|
|
||||||
f" Тип: {release_type}\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
# Send release images if available
|
|
||||||
for release in results["results"][:3]: # Send images for first 3 results
|
|
||||||
image = release.get("image")
|
|
||||||
if image and isinstance(image, dict):
|
|
||||||
image_url = image.get("url")
|
|
||||||
if image_url:
|
|
||||||
try:
|
|
||||||
title = release.get("title", "Release")
|
|
||||||
await update.message.reply_photo(
|
|
||||||
photo=f"https://t.vndb.org{image_url}",
|
|
||||||
caption=f"{title}",
|
|
||||||
parse_mode="Markdown"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Could not send release image: {e}")
|
|
||||||
|
|
||||||
context.user_data["release_results"] = results["results"]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching releases: {e}")
|
logger.error(e)
|
||||||
error_msg = ErrorHandler.format_error(e)
|
await update.message.reply_text(f"❌ {ErrorHandler.format_error(e)}")
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def search_staff(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
|
||||||
"""Search for staff members"""
|
|
||||||
try:
|
|
||||||
args = " ".join(context.args) if context.args else ""
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
await update.message.reply_text(
|
|
||||||
"Пожалуйста, укажите имя\n"
|
|
||||||
"Пример: /staff Yoko"
|
|
||||||
)
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
await update.message.reply_text(f"Поиск сотрудников: **{args}**\nЗагрузка...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
filters = ["search", "=", args]
|
|
||||||
results = await vndb_client.query_staff(
|
|
||||||
filters=[filters],
|
|
||||||
fields=["name", "original", "gender", "role"],
|
|
||||||
results=10
|
|
||||||
)
|
|
||||||
|
|
||||||
if not results.get("results"):
|
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
response_text = f"**Результаты поиска сотрудников: {args}**\n\n"
|
|
||||||
|
|
||||||
for i, staff in enumerate(results["results"], 1):
|
|
||||||
staff_id = staff.get("id", "Unknown")
|
|
||||||
name = staff.get("name", "Unknown")
|
|
||||||
original = staff.get("original", "")
|
|
||||||
gender = staff.get("gender", "Unknown")
|
|
||||||
|
|
||||||
response_text += (
|
|
||||||
f"{i}. **{name}**\n"
|
|
||||||
f" ID: {staff_id}\n"
|
|
||||||
)
|
|
||||||
if original:
|
|
||||||
response_text += f" Оригинал: {original}\n"
|
|
||||||
response_text += f" Пол: {gender}\n\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
context.user_data["staff_results"] = results["results"]
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error searching staff: {e}")
|
|
||||||
error_msg = ErrorHandler.format_error(e)
|
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def search_producer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
|
||||||
"""Search for producers"""
|
|
||||||
try:
|
|
||||||
args = " ".join(context.args) if context.args else ""
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
await update.message.reply_text(
|
|
||||||
"Пожалуйста, укажите название\n"
|
|
||||||
"Пример: /producer Key"
|
|
||||||
)
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
await update.message.reply_text(f"Поиск продюсеров: **{args}**\n⏳ Загрузка...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
filters = ["search", "=", args]
|
|
||||||
results = await vndb_client.query_producer(
|
|
||||||
filters=[filters],
|
|
||||||
fields=["name", "original", "type"],
|
|
||||||
results=10
|
|
||||||
)
|
|
||||||
|
|
||||||
if not results.get("results"):
|
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
response_text = f"**Результаты поиска продюсеров: {args}**\n\n"
|
|
||||||
|
|
||||||
for i, producer in enumerate(results["results"], 1):
|
|
||||||
producer_id = producer.get("id", "Unknown")
|
|
||||||
name = producer.get("name", "Unknown")
|
|
||||||
original = producer.get("original", "")
|
|
||||||
producer_type = producer.get("type", "Unknown")
|
|
||||||
|
|
||||||
response_text += (
|
|
||||||
f"{i}. **{name}**\n"
|
|
||||||
f" ID: {producer_id}\n"
|
|
||||||
)
|
|
||||||
if original:
|
|
||||||
response_text += f" Оригинал: {original}\n"
|
|
||||||
response_text += f" Тип: {producer_type}\n\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error searching producers: {e}")
|
|
||||||
error_msg = ErrorHandler.format_error(e)
|
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
return ConversationHandler.END
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def list_tags(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""List popular tags"""
|
|
||||||
try:
|
|
||||||
await update.message.reply_text("Загружаю теги...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
results = await vndb_client.query_tag(
|
|
||||||
fields=["name", "description"],
|
|
||||||
sort="vns",
|
|
||||||
reverse=True,
|
|
||||||
results=15
|
|
||||||
)
|
|
||||||
|
|
||||||
if not results.get("results"):
|
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return
|
|
||||||
|
|
||||||
response_text = "**Популярные теги VNDB:**\n\n"
|
|
||||||
|
|
||||||
for i, tag in enumerate(results["results"], 1):
|
|
||||||
tag_id = tag.get("id", "Unknown")
|
|
||||||
name = tag.get("name", "Unknown")
|
|
||||||
description = tag.get("description", "")
|
|
||||||
|
|
||||||
response_text += f"{i}. **{name}** (`{tag_id}`)\n"
|
|
||||||
if description and len(description) < 50:
|
|
||||||
response_text += f" {description}\n"
|
|
||||||
response_text += "\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error listing tags: {e}")
|
|
||||||
error_msg = ErrorHandler.format_error(e)
|
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def list_traits(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""List character traits"""
|
|
||||||
try:
|
|
||||||
await update.message.reply_text("Загружаю черты характера...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
results = await vndb_client.query_trait(
|
|
||||||
fields=["name", "description"],
|
|
||||||
sort="chars",
|
|
||||||
reverse=True,
|
|
||||||
results=15
|
|
||||||
)
|
|
||||||
|
|
||||||
if not results.get("results"):
|
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return
|
|
||||||
|
|
||||||
response_text = "**Популярные черты характера:**\n\n"
|
|
||||||
|
|
||||||
for i, trait in enumerate(results["results"], 1):
|
|
||||||
trait_id = trait.get("id", "Unknown")
|
|
||||||
name = trait.get("name", "Unknown")
|
|
||||||
description = trait.get("description", "")
|
|
||||||
|
|
||||||
response_text += f"{i}. **{name}** (`{trait_id}`)\n"
|
|
||||||
if description and len(description) < 50:
|
|
||||||
response_text += f" {description}\n"
|
|
||||||
response_text += "\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error listing traits: {e}")
|
|
||||||
error_msg = ErrorHandler.format_error(e)
|
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_quote(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""Get random quotes"""
|
|
||||||
try:
|
|
||||||
count = 1
|
|
||||||
if context.args and context.args[0].isdigit():
|
|
||||||
count = min(int(context.args[0]), 5) # Max 5 quotes
|
|
||||||
|
|
||||||
await update.message.reply_text(f"Загружаю {count} цитат...", parse_mode="Markdown")
|
|
||||||
|
|
||||||
results = await vndb_client.query_quote(
|
|
||||||
fields=["character", "quote"],
|
|
||||||
sort="id",
|
|
||||||
results=count
|
|
||||||
)
|
|
||||||
|
|
||||||
if not results.get("results"):
|
|
||||||
await update.message.reply_text("Ничего не найдено")
|
|
||||||
return
|
|
||||||
|
|
||||||
response_text = "**Случайные цитаты:**\n\n"
|
|
||||||
|
|
||||||
for quote in results["results"]:
|
|
||||||
quote_text = quote.get("quote", "")
|
|
||||||
character = quote.get("character", "Unknown")
|
|
||||||
|
|
||||||
if quote_text:
|
|
||||||
response_text += f"_{quote_text}_\n"
|
|
||||||
response_text += f"— **{character}**\n\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error getting quotes: {e}")
|
|
||||||
error_msg = ErrorHandler.format_error(e)
|
|
||||||
await update.message.reply_text(f"{error_msg}")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def authinfo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
||||||
"""Get authentication info"""
|
|
||||||
try:
|
|
||||||
token = Config.VNDB_TOKEN
|
|
||||||
if not token:
|
|
||||||
await update.message.reply_text(
|
|
||||||
"Токен VNDB не установлен\n\n"
|
|
||||||
"Чтобы использовать функции авторизации:\n"
|
|
||||||
"1. Посетите https://vndb.org/u/tokens\n"
|
|
||||||
"2. Создайте новый токен\n"
|
|
||||||
"3. Установите переменную окружения VNDB_TOKEN"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
client_with_token = VndbClient(token=token, use_sandbox=Config.USE_SANDBOX)
|
|
||||||
auth_info = await client_with_token.get_authinfo()
|
|
||||||
|
|
||||||
response_text = f"""
|
|
||||||
\*\*Информация об авторизации:\*\*
|
|
||||||
|
|
||||||
ID: {auth_info.get('id', 'Unknown')}
|
|
||||||
Пользователь: {auth_info.get('username', 'Unknown')}
|
|
||||||
|
|
||||||
\*\*Разрешения:\*\*
|
|
||||||
"""
|
|
||||||
|
|
||||||
permissions = auth_info.get("permissions", [])
|
|
||||||
if "listread" in permissions:
|
|
||||||
response_text += "Чтение списка (listread)\n"
|
|
||||||
if "listwrite" in permissions:
|
|
||||||
response_text += "Запись в список (listwrite)\n"
|
|
||||||
|
|
||||||
if not permissions:
|
|
||||||
response_text += "Нет разрешений"
|
|
||||||
|
|
||||||
await update.message.reply_text(response_text, parse_mode="Markdown")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error getting authinfo: {e}")
|
|
||||||
error_msg = ErrorHandler.format_error(e)
|
|
||||||
await update.message.reply_text(error_msg)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
# ======================
|
||||||
"""Start the bot"""
|
# MAIN
|
||||||
# Validate configuration
|
# ======================
|
||||||
try:
|
def main():
|
||||||
Config.validate()
|
Config.validate()
|
||||||
except ValueError as e:
|
|
||||||
logger.error(f"Configuration error: {e}")
|
app = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
|
||||||
raise
|
|
||||||
|
app.add_handler(CommandHandler("start", BotHandlers.start))
|
||||||
logger.info(f"Starting bot with config: {Config.to_dict()}")
|
app.add_handler(CommandHandler("help", BotHandlers.help_command))
|
||||||
|
app.add_handler(CommandHandler("stats", BotHandlers.stats))
|
||||||
# Create application
|
app.add_handler(CommandHandler("schema", BotHandlers.schema))
|
||||||
application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
|
app.add_handler(CommandHandler("search", BotHandlers.search_vn))
|
||||||
|
app.add_handler(CommandHandler("char", BotHandlers.search_character))
|
||||||
# Add handlers
|
app.add_handler(CommandHandler("release", BotHandlers.search_release))
|
||||||
application.add_handler(CommandHandler("start", BotHandlers.start))
|
|
||||||
application.add_handler(CommandHandler("help", BotHandlers.help_command))
|
for h in get_detail_handlers():
|
||||||
application.add_handler(CommandHandler("stats", BotHandlers.stats))
|
app.add_handler(h)
|
||||||
application.add_handler(CommandHandler("schema", BotHandlers.schema))
|
|
||||||
application.add_handler(CommandHandler("search", BotHandlers.search_vn))
|
logger.info("Bot started")
|
||||||
application.add_handler(CommandHandler("char", BotHandlers.search_character))
|
app.run_polling()
|
||||||
application.add_handler(CommandHandler("release", BotHandlers.search_release))
|
|
||||||
application.add_handler(CommandHandler("staff", BotHandlers.search_staff))
|
|
||||||
application.add_handler(CommandHandler("producer", BotHandlers.search_producer))
|
|
||||||
application.add_handler(CommandHandler("tag", BotHandlers.list_tags))
|
|
||||||
application.add_handler(CommandHandler("trait", BotHandlers.list_traits))
|
|
||||||
application.add_handler(CommandHandler("quote", BotHandlers.get_quote))
|
|
||||||
application.add_handler(CommandHandler("authinfo", BotHandlers.authinfo))
|
|
||||||
|
|
||||||
# Add detailed handlers for viewing with images
|
|
||||||
for handler in get_detail_handlers():
|
|
||||||
application.add_handler(handler)
|
|
||||||
|
|
||||||
# Start the bot
|
|
||||||
logger.info("Starting bot...")
|
|
||||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user