Files
ayako/bot.py

160 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
def clean_text(text: str) -> str:
"""Очистка текста от тегов VNDB и ограничение длины."""
if not text: return "No description available."
text = re.sub(r'\[url=?.*?\](.*?)\[/url\]', r'\1', text)
text = re.sub(r'\[/?b\]', '', text)
text = re.sub(r'\[/?i\]', '', text)
text = text.replace('"', "'")
return text[:700] + "..." if len(text) > 700 else text
def safe_list_to_str(data) -> str:
"""Безопасное преобразование данных в строку через запятую."""
if not data: return "N/A"
if isinstance(data, list):
# Если элементы списка — строки
if all(isinstance(x, str) for x in data):
return ", ".join([x.upper() for x in data])
# Если элементы списка — словари (как в languages у релизов)
processed = []
for x in data:
if isinstance(x, dict):
# берем lang или name, что найдется
val = x.get('lang') or x.get('name')
if val: processed.append(str(val).upper())
return ", ".join(processed) if processed else "N/A"
return str(data).upper()
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"Connection error: {e}")
return None
@dp.message(Command("vn"))
async def handle_vn(message: types.Message, command: CommandObject):
if not command.args: return await message.answer("Example: /vn Steins Gate")
is_id = command.args.startswith('v') and command.args[1:].isdigit()
filt = ["id", "=", command.args] if is_id else ["search", "=", command.args]
# Для VN languages — это [string]
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 is_id:
out = ["🔍 <b>Select VN by ID:</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]
text = (
f"<b>TITLE:</b> {v['title']}\n"
f"<b>ORIGINAL:</b> {v.get('alttitle') or 'N/A'}\n\n"
f"<b>RELEASED:</b> {v.get('released', 'N/A')}\n"
f"<b>DEVELOPER:</b> {safe_list_to_str(v.get('developers'))}\n"
f"<b>RATING:</b> {v['rating']/10 if v.get('rating') else 'N/A'} ({v.get('votecount', 0)} votes)\n"
f"<b>LANGUAGES:</b> {safe_list_to_str(v.get('languages'))}\n"
f"<b>PLATFORMS:</b> {safe_list_to_str(v.get('platforms'))}\n\n"
f"<b>DESCRIPTION:</b>\n<i>{clean_text(v.get('description'))}</i>\n\n"
f"<b>VNDB LINK:</b> 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("Example: /char Kurisu")
is_id = command.args.startswith('c') and command.args[1:].isdigit()
filt = ["id", "=", command.args] if is_id 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 is_id:
out = ["👤 <b>Select Character by ID:</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]
# Защита от list в поле gender
raw_gender = c.get('gender')
if isinstance(raw_gender, list): raw_gender = raw_gender[0] if raw_gender else None
gender = {"m": "MALE", "f": "FEMALE", "both": "BOTH"}.get(raw_gender, 'UNKNOWN')
text = (
f"<b>NAME:</b> {c['name']}\n"
f"<b>ORIGINAL:</b> {c.get('original') or 'N/A'}\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<i>{clean_text(c.get('description'))}</i>\n\n"
f"<b>VNDB LINK:</b> 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("Example: /release Steins Gate")
is_id = command.args.startswith('r') and command.args[1:].isdigit()
filt = ["id", "=", command.args] if is_id else ["search", "=", command.args]
# КРИТИЧНО: languages{lang} для релизов
fields = "id, title, alttitle, released, languages{lang}, 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 is_id:
out = ["💿 <b>Select Release by ID:</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', [])[:8]])
text = (
f"<b>RELEASE TITLE:</b> {r['title']}\n"
f"<b>ORIGINAL:</b> {r.get('alttitle') or 'N/A'}\n\n"
f"<b>DATE:</b> {r.get('released', 'N/A')}\n"
f"<b>LANGUAGES:</b> {safe_list_to_str(r.get('languages'))}\n"
f"<b>PLATFORMS:</b> {safe_list_to_str(r.get('platforms'))}\n\n"
f"<b>LINKS / STORES:</b>\n{links or 'No links available'}\n\n"
f"<b>VNDB LINK:</b> 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 Technical Bot</b>\n\n/vn [name/id]\n/char [name/id]\n/release [name/id]")
if __name__ == "__main__":
dp.run_polling(bot)