Files
ayako/bot.py

141 lines
6.4 KiB
Python

import os
import logging
import re
import httpx
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command, CommandObject
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
TOKEN = os.getenv("TELEGRAM_TOKEN")
API_URL = os.getenv("VNDB_API_URL", "https://api.vndb.org/kana")
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
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):
payload = {"filters": filters, "fields": fields}
async with httpx.AsyncClient(timeout=15.0) as client:
try:
response = await client.post(f"{API_URL}/{endpoint}", json=payload)
if response.status_code == 200:
return response.json().get("results", [])
logger.error(f"VNDB Error {response.status_code}: {response.text}")
return None
except Exception as e:
logger.error(f"Request failed: {e}")
return None
@dp.message(Command("vn"))
async def handle_vn(message: types.Message, command: CommandObject):
if not command.args: return await message.answer("Usage: /vn <name/id>")
filt = ["id", "=", command.args] if command.args.startswith('v') and command.args[1:].isdigit() else ["search", "=", command.args]
# Запрашиваем расширенные поля
fields = "id, title, alttitle, released, rating, votecount, description, languages, platforms, developers{name}"
res = await fetch_vndb("vn", filt, fields)
if not res: return await message.answer("❌ Not found.")
if len(res) > 1 and not command.args.startswith('v'):
out = ["🔍 <b>Multiple results:</b>"]
for i in res[:10]: out.append(f"{i['title']} (<code>{i['id']}</code>)")
return await message.answer("\n".join(out))
v = res[0]
langs = " ".join([LANG_FLAGS.get(l, l.upper()) for l in v.get('languages', [])])
plats = " ".join([PLATFORM_ICONS.get(p, p.upper()) for p in v.get('platforms', [])])
devs = ", ".join([d['name'] for d in v.get('developers', [])])
text = (
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)
@dp.message(Command("char"))
async def handle_char(message: types.Message, command: CommandObject):
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]
fields = "id, name, original, description, gender, age, blood_type"
res = await fetch_vndb("character", filt, fields)
if not res: return await message.answer("❌ Not found.")
if len(res) > 1 and not command.args.startswith('c'):
out = ["👤 <b>Multiple results:</b>"]
for i in res[:10]: out.append(f"{i['name']} (<code>{i['id']}</code>)")
return await message.answer("\n".join(out))
c = res[0]
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"))
async def handle_rel(message: types.Message, command: CommandObject):
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]
fields = "id, title, alttitle, released, languages, platforms, extlinks{url, label}"
res = await fetch_vndb("release", filt, fields)
if not res: return await message.answer("❌ Not found.")
if len(res) > 1 and not command.args.startswith('r'):
out = ["💿 <b>Multiple results:</b>"]
for i in res[:10]: out.append(f"{i['title']} (<code>{i['id']}</code>)")
return await message.answer("\n".join(out))
r = res[0]
links = "\n".join([f"🔗 <a href='{l['url']}'>{l['label']}</a>" for l in r.get('extlinks', [])[:5]])
text = (
f"💿 <b>{r['title']}</b>\n"
f"<i>{r.get('alttitle', '')}</i>\n\n"
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__":
dp.run_polling(bot)