Улучшение обработки команд: добавление новых полей в запросы, улучшение сообщений об ошибках и рефакторинг функций для обработки данных

This commit is contained in:
2026-05-01 18:30:37 +03:00
parent 602952ff8d
commit c3afb260f0

120
bot.py
View File

@@ -1,5 +1,6 @@
import os import os
import logging import logging
import re
import httpx import httpx
from aiogram import Bot, Dispatcher, types from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command, CommandObject from aiogram.filters import Command, CommandObject
@@ -15,97 +16,126 @@ logger = logging.getLogger(__name__)
bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
dp = Dispatcher() dp = Dispatcher()
# Словари для красоты
LANG_FLAGS = {"en": "🇺🇸", "ja": "🇯🇵", "ru": "🇷🇺", "zh": "🇨🇳", "ko": "🇰🇷", "fr": "🇫🇷", "de": "🇩🇪"}
PLATFORM_ICONS = {"win": "🖥️", "swi": "🎮", "ps4": "🟦", "and": "📱", "ios": "🍏"}
def clean_text(text: str) -> str:
"""Очистка описаний от специфических тегов VNDB [url], [b] и т.д."""
if not text: return "No description available."
text = re.sub(r'\[url=?.*?\](.*?)\[/url\]', r'\1', text)
text = text.replace("[b]", "<b>").replace("[/b]", "</b>")
text = text.replace("[i]", "<i>").replace("[/i]", "</i>")
return text[:600] + "..." if len(text) > 600 else text
async def fetch_vndb(endpoint: str, filters: list, fields: str): async def fetch_vndb(endpoint: str, filters: list, fields: str):
payload = {"filters": filters, "fields": fields} payload = {"filters": filters, "fields": fields}
async with httpx.AsyncClient(timeout=10.0) as client: async with httpx.AsyncClient(timeout=15.0) as client:
try: try:
response = await client.post(f"{API_URL}/{endpoint}", json=payload) response = await client.post(f"{API_URL}/{endpoint}", json=payload)
if response.status_code == 200: if response.status_code == 200:
return response.json().get("results", []) return response.json().get("results", [])
logger.error(f"VNDB Error {response.status_code} on {endpoint}: {response.text}") logger.error(f"VNDB Error {response.status_code}: {response.text}")
return None return None
except Exception as e: except Exception as e:
logger.error(f"Request failed: {e}") logger.error(f"Request failed: {e}")
return None return None
@dp.message(Command("start", "help"))
async def cmd_help(message: types.Message):
await message.answer(
"🤖 <b>VNDB Bot</b>\n\n"
"• /vn &lt;id или название&gt;\n"
"• /char &lt;id или название&gt;\n"
"• /release &lt;id или название&gt;"
)
@dp.message(Command("vn")) @dp.message(Command("vn"))
async def handle_vn(message: types.Message, command: CommandObject): async def handle_vn(message: types.Message, command: CommandObject):
if not command.args: return await message.answer("Введите название или ID (v17)") if not command.args: return await message.answer("Usage: /vn <name/id>")
# Если это ID (начинается с 'v' + цифры) filt = ["id", "=", command.args] if command.args.startswith('v') and command.args[1:].isdigit() else ["search", "=", command.args]
if command.args.startswith('v') and command.args[1:].isdigit(): # Запрашиваем расширенные поля
filt = ["id", "=", command.args] fields = "id, title, alttitle, released, rating, votecount, description, languages, platforms, developers{name}"
else: res = await fetch_vndb("vn", filt, fields)
filt = ["search", "=", command.args]
# Важно: для VN используем alttitle вместо original if not res: return await message.answer("❌ Not found.")
res = await fetch_vndb("vn", filt, "id, title, alttitle, released, rating, votecount")
if not res: return await message.answer("❌ Ничего не найдено.")
if len(res) > 1 and not command.args.startswith('v'): if len(res) > 1 and not command.args.startswith('v'):
out = ["🔍 <b>Результаты поиска:</b>"] out = ["🔍 <b>Multiple results:</b>"]
for i in res[:10]: for i in res[:10]: out.append(f"{i['title']} (<code>{i['id']}</code>)")
out.append(f"{i['title']} (<code>{i['id']}</code>)")
return await message.answer("\n".join(out)) return await message.answer("\n".join(out))
v = res[0] v = res[0]
rating = f"{v['rating']/10}" if v.get('rating') else "N/A" langs = " ".join([LANG_FLAGS.get(l, l.upper()) for l in v.get('languages', [])])
text = (f"📖 <b>{v['title']}</b>\n" plats = " ".join([PLATFORM_ICONS.get(p, p.upper()) for p in v.get('platforms', [])])
f"Original: {v.get('alttitle', 'N/A')}\n" devs = ", ".join([d['name'] for d in v.get('developers', [])])
f"Released: {v.get('released', 'N/A')}\n"
f"Rating: {rating} ({v.get('votecount', 0)} votes)\n" text = (
f"https://vndb.org/{v['id']}") f"📖 <b>{v['title']}</b>\n"
f"<i>{v.get('alttitle', '')}</i>\n\n"
f"🗓 <b>Released:</b> {v.get('released', 'N/A')}\n"
f"⭐ <b>Rating:</b> {v['rating']/10 if v.get('rating') else 'N/A'} ({v.get('votecount', 0)} votes)\n"
f"🏢 <b>Developer:</b> {devs or 'N/A'}\n"
f"🌍 <b>Langs:</b> {langs}\n"
f"🎮 <b>Platforms:</b> {plats}\n\n"
f"📝 <b>Description:</b>\n{clean_text(v.get('description'))}\n\n"
f"🔗 https://vndb.org/{v['id']}"
)
await message.answer(text) await message.answer(text)
@dp.message(Command("char")) @dp.message(Command("char"))
async def handle_char(message: types.Message, command: CommandObject): async def handle_char(message: types.Message, command: CommandObject):
if not command.args: return await message.answer("Введите имя или ID (c1)") if not command.args: return await message.answer("Usage: /char <name/id>")
filt = ["id", "=", command.args] if command.args.startswith('c') and command.args[1:].isdigit() else ["search", "=", command.args] filt = ["id", "=", command.args] if command.args.startswith('c') and command.args[1:].isdigit() else ["search", "=", command.args]
fields = "id, name, original, description, gender, age, blood_type"
res = await fetch_vndb("character", filt, fields)
# Для персонажей original работает if not res: return await message.answer("❌ Not found.")
res = await fetch_vndb("character", filt, "id, name, original")
if not res: return await message.answer("Не найдено.")
if len(res) > 1 and not command.args.startswith('c'): if len(res) > 1 and not command.args.startswith('c'):
out = ["👤 <b>Персонажи:</b>"] out = ["👤 <b>Multiple results:</b>"]
for i in res[:10]: out.append(f"{i['name']} (<code>{i['id']}</code>)") for i in res[:10]: out.append(f"{i['name']} (<code>{i['id']}</code>)")
return await message.answer("\n".join(out)) return await message.answer("\n".join(out))
c = res[0] c = res[0]
await message.answer(f"👤 <b>{c['name']}</b>\nOriginal: {c.get('original', 'N/A')}\nhttps://vndb.org/{c['id']}") gender = {"m": "Male ♂️", "f": "Female ♀️", "both": "Both 🚻"}.get(c.get('gender'), 'Unknown')
text = (
f"👤 <b>{c['name']}</b> ({c.get('original', '')})\n\n"
f"⚧ <b>Gender:</b> {gender}\n"
f"🎂 <b>Age:</b> {c.get('age') or 'Unknown'}\n"
f"🩸 <b>Blood Type:</b> {c.get('blood_type') or 'N/A'}\n\n"
f"📝 <b>Description:</b>\n{clean_text(c.get('description'))}\n\n"
f"🔗 https://vndb.org/{c['id']}"
)
await message.answer(text)
@dp.message(Command("release")) @dp.message(Command("release"))
async def handle_rel(message: types.Message, command: CommandObject): async def handle_rel(message: types.Message, command: CommandObject):
if not command.args: return await message.answer("Введите название или ID (r1)") if not command.args: return await message.answer("Usage: /release <name/id>")
filt = ["id", "=", command.args] if command.args.startswith('r') and command.args[1:].isdigit() else ["search", "=", command.args] filt = ["id", "=", command.args] if command.args.startswith('r') and command.args[1:].isdigit() else ["search", "=", command.args]
fields = "id, title, alttitle, released, languages, platforms, extlinks{url, label}"
res = await fetch_vndb("release", filt, fields)
res = await fetch_vndb("release", filt, "id, title, alttitle, released") if not res: return await message.answer("❌ Not found.")
if not res: return await message.answer("Не найдено.")
if len(res) > 1 and not command.args.startswith('r'): if len(res) > 1 and not command.args.startswith('r'):
out = ["💿 <b>Релизы:</b>"] out = ["💿 <b>Multiple results:</b>"]
for i in res[:10]: out.append(f"{i['title']} (<code>{i['id']}</code>)") for i in res[:10]: out.append(f"{i['title']} (<code>{i['id']}</code>)")
return await message.answer("\n".join(out)) return await message.answer("\n".join(out))
r = res[0] r = res[0]
await message.answer(f"💿 <b>{r['title']}</b>\nReleased: {r.get('released', 'N/A')}\nhttps://vndb.org/{r['id']}") links = "\n".join([f"🔗 <a href='{l['url']}'>{l['label']}</a>" for l in r.get('extlinks', [])[:5]])
# Прямые команды поиска (алиасы для удобства) text = (
@dp.message(Command("search")) f"💿 <b>{r['title']}</b>\n"
async def search_alias(message: types.Message, command: CommandObject): f"<i>{r.get('alttitle', '')}</i>\n\n"
await handle_vn(message, command) f"🗓 <b>Released:</b> {r.get('released', 'N/A')}\n"
f"🌍 <b>Langs:</b> {' '.join([LANG_FLAGS.get(l, l.upper()) for l in r.get('languages', [])])}\n\n"
f"<b>Links & Stores:</b>\n{links or 'No links available'}\n\n"
f"🔗 https://vndb.org/{r['id']}"
)
await message.answer(text, disable_web_page_preview=True)
@dp.message(Command("start", "help", "search"))
async def cmd_start(message: types.Message, command: CommandObject):
if message.text.startswith("/search") and command.args:
return await handle_vn(message, command)
await message.answer("🤖 <b>VNDB Bot</b>\n/vn [id/name]\n/char [id/name]\n/release [id/name]")
if __name__ == "__main__": if __name__ == "__main__":
dp.run_polling(bot) dp.run_polling(bot)