From 597be864af09925c4ed42f8224163dddbf7fe069 Mon Sep 17 00:00:00 2001 From: King-of-the-all-Cookies Date: Fri, 1 May 2026 18:37:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4:=20=D1=80=D0=B5?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20safe=5Flist=5Fto=5Fstr,?= =?UTF-8?q?=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B8=20=D1=83=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 61 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/bot.py b/bot.py index 0718324..38afd34 100644 --- a/bot.py +++ b/bot.py @@ -17,15 +17,31 @@ bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) dp = Dispatcher() def clean_text(text: str) -> str: - """Очистка текста от VNDB-тегов и ограничение длины.""" + """Очистка текста от тегов VNDB и ограничение длины.""" if not text: return "No description available." - # Убираем [url] и другие теги 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: @@ -46,31 +62,25 @@ async def handle_vn(message: types.Message, command: CommandObject): 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.") - - # Если поиск выдал список, а не конкретный ID if len(res) > 1 and not is_id: out = ["🔍 Select VN by ID:"] - for i in res[:10]: - out.append(f"• {i['title']} — {i['id']}") + for i in res[:10]: out.append(f"• {i['title']} — {i['id']}") return await message.answer("\n".join(out)) v = res[0] - langs = ", ".join([l.upper() for l in v.get('languages', [])]) - plats = ", ".join([p.upper() for p in v.get('platforms', [])]) - devs = ", ".join([d['name'] for d in v.get('developers', [])]) - text = ( f"TITLE: {v['title']}\n" f"ORIGINAL: {v.get('alttitle') or 'N/A'}\n\n" f"RELEASED: {v.get('released', 'N/A')}\n" - f"DEVELOPER: {devs or 'N/A'}\n" + f"DEVELOPER: {safe_list_to_str(v.get('developers'))}\n" f"RATING: {v['rating']/10 if v.get('rating') else 'N/A'} ({v.get('votecount', 0)} votes)\n" - f"LANGUAGES: {langs or 'N/A'}\n" - f"PLATFORMS: {plats or 'N/A'}\n\n" + f"LANGUAGES: {safe_list_to_str(v.get('languages'))}\n" + f"PLATFORMS: {safe_list_to_str(v.get('platforms'))}\n\n" f"DESCRIPTION:\n{clean_text(v.get('description'))}\n\n" f"VNDB LINK: https://vndb.org/{v['id']}" ) @@ -87,14 +97,16 @@ async def handle_char(message: types.Message, command: CommandObject): 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 = ["👤 Select Character by ID:"] for i in res[:10]: out.append(f"• {i['name']} — {i['id']}") return await message.answer("\n".join(out)) c = res[0] - gender = {"m": "MALE", "f": "FEMALE", "both": "BOTH"}.get(c.get('gender'), 'UNKNOWN') + # Защита от 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"NAME: {c['name']}\n" @@ -114,28 +126,25 @@ async def handle_rel(message: types.Message, command: CommandObject): is_id = command.args.startswith('r') and command.args[1:].isdigit() filt = ["id", "=", command.args] if is_id else ["search", "=", command.args] - fields = "id, title, alttitle, released, languages, platforms, extlinks{url, label}" + # КРИТИЧНО: 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 = ["💿 Select Release by ID:"] for i in res[:10]: out.append(f"• {i['title']} — {i['id']}") return await message.answer("\n".join(out)) r = res[0] - langs = ", ".join([l.upper() for l in r.get('languages', [])]) - plats = ", ".join([p.upper() for p in r.get('platforms', [])]) - # Ограничиваем ссылки, чтобы не спамить links = "\n".join([f"• {l['label']}" for l in r.get('extlinks', [])[:8]]) text = ( f"RELEASE TITLE: {r['title']}\n" f"ORIGINAL: {r.get('alttitle') or 'N/A'}\n\n" f"DATE: {r.get('released', 'N/A')}\n" - f"LANGUAGES: {langs}\n" - f"PLATFORMS: {plats}\n\n" + f"LANGUAGES: {safe_list_to_str(r.get('languages'))}\n" + f"PLATFORMS: {safe_list_to_str(r.get('platforms'))}\n\n" f"LINKS / STORES:\n{links or 'No links available'}\n\n" f"VNDB LINK: https://vndb.org/{r['id']}" ) @@ -145,13 +154,7 @@ async def handle_rel(message: types.Message, command: CommandObject): 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( - "🤖 VNDB Technical Bot\n\n" - "Commands:\n" - "/vn [name/id]\n" - "/char [name/id]\n" - "/release [name/id]" - ) + await message.answer("🤖 VNDB Technical Bot\n\n/vn [name/id]\n/char [name/id]\n/release [name/id]") if __name__ == "__main__": dp.run_polling(bot) \ No newline at end of file