From c1d26fb369aa4632ad98f46f932cd0561ae1d661 Mon Sep 17 00:00:00 2001 From: King-of-the-all-Cookies Date: Fri, 1 May 2026 18:17:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B5=D0=B9=20=D0=B8=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D1=80=D1=8B=20=D0=BA=D0=BE=D0=B4=D0=B0:=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0=20Telegram,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F,=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=B3=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D0=BE=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD?= =?UTF-8?q?=D0=B4=20=D0=B8=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9?= =?UTF-8?q?=D0=BA=D0=B0=20Docker.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- Dockerfile | 5 +- bot.py | 170 ++++++++++++++++++++++----------------------- docker-compose.yml | 10 ++- requirements.txt | 4 +- 5 files changed, 97 insertions(+), 94 deletions(-) diff --git a/.env.example b/.env.example index 5dcf55e..0ef48b3 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ -TELEGRAM_TOKEN=your_token_here +TELEGRAM_TOKEN=your_bot_token_here VNDB_API_URL=https://api.vndb.org/kana \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a62dff5..cd90a61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,12 @@ FROM python:3.12-slim WORKDIR /app +# Установка зависимостей COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY . . +# Копирование кода +COPY bot.py . +# Запуск CMD ["python", "bot.py"] \ No newline at end of file diff --git a/bot.py b/bot.py index 0761bb6..c8804aa 100644 --- a/bot.py +++ b/bot.py @@ -1,111 +1,105 @@ import os -import asyncio -from aiogram import Bot, Dispatcher, types +import logging +import httpx +from aiogram import Bot, Dispatcher, types, F from aiogram.filters import Command -from vndb import VNDBClient +from aiogram.utils.markdown import hbold, hlink +# Настройки TOKEN = os.getenv("TELEGRAM_TOKEN") +API_URL = os.getenv("VNDB_API_URL", "https://api.vndb.org/kana") -bot = Bot(token=TOKEN) +# Логирование +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +bot = Bot(token=TOKEN, parse_mode="HTML") dp = Dispatcher() -vndb = VNDBClient(os.getenv("VNDB_API_URL")) +# Хелпер для запросов к VNDB +async def fetch_vndb(endpoint: str, filters: list, fields: str): + payload = {"filters": filters, "fields": fields} + async with httpx.AsyncClient(timeout=10.0) as client: + try: + response = await client.post(f"{API_URL}/{endpoint}", json=payload) + if response.status_code != 200: + logger.error(f"VNDB Error {response.status_code}: {response.text}") + return None + return response.json().get("results", []) + except Exception as e: + logger.error(f"Connection error: {e}") + return None -def safe_text(x): - if not x: - return "—" - return str(x) - - -@dp.message(Command("start")) -async def start(msg: types.Message): - await msg.answer( - "VNDB Bot ready.\n" - "Commands:\n" - "/search \n" - "/vn \n" - "/char \n" - "/release " +# Команды /start и /help +@dp.message(Command("start", "help")) +async def cmd_start(message: types.Message): + help_text = ( + "🤖 VNDB Bot\n\n" + "Поиск:\n" + "/search [name] - поиск новеллы\n" + "/char [name] - поиск персонажа\n" + "/release [name] - поиск релизов\n\n" + "Инфо по ID:\n" + "/vn [id] (напр. v17)\n" + "/char_id [id] (напр. c1)\n" + "/rel_id [id] (напр. r1)" ) + await message.answer(help_text) - -@dp.message(Command("help")) -async def help(msg: types.Message): - await start(msg) - - +# Обработчик поиска VN @dp.message(Command("search")) -async def search(msg: types.Message): - query = msg.text.replace("/search", "").strip() +async def search_vn(message: types.Message): + query = message.extract_args() if not query: - return await msg.answer("Empty query") + return await message.answer("Введите название для поиска. Пример: /search Steins;Gate") - result = await vndb.search_vn(query) - - if not result: - return await msg.answer("Not found") - - text = "\n\n".join( - f"{i['id']} — {i.get('title') or i.get('name')}" - for i in result - ) - await msg.answer(text[:4000]) + res = await fetch_vndb("vn", ["search", "=", query], "title, released, rating") + if not res: + return await message.answer("❌ Ничего не найдено или ошибка API") + out = [f"🔍 Результаты поиска:"] + for item in res[:10]: + rating = f"⭐ {item['rating']/10}" if item.get('rating') else "No rating" + out.append(f"• {item['title']} (ID: {item['id']}) | {rating}") + + await message.answer("\n".join(out)) +# Обработчик деталей VN @dp.message(Command("vn")) -async def vn(msg: types.Message): - vid = msg.text.replace("/vn", "").strip() - if not vid: - return await msg.answer("No ID") +async def details_vn(message: types.Message): + vn_id = message.extract_args() + if not vn_id: return await message.answer("Укажите ID. Пример: /vn v11") - data = await vndb.get_vn(vid) - if not data: - return await msg.answer("Not found") + res = await fetch_vndb("vn", ["id", "=", vn_id], "id, title, original, released, rating, votecount") + if not res: return await message.answer("❌ VN не найдена") - await msg.answer( - f"{safe_text(data.get('title'))}\n" - f"Original: {safe_text(data.get('original'))}\n" - f"Released: {safe_text(data.get('released'))}\n" - f"Rating: {safe_text(data.get('rating'))}\n" - f"Votes: {safe_text(data.get('votecount'))}" + v = res[0] + text = ( + f"📖 {v['title']}\n" + f"Original: {v.get('original', 'N/A')}\n" + f"Released: {v.get('released', 'N/A')}\n" + f"Rating: {v.get('rating', 'N/A') if v.get('rating') else 'N/A'} ({v.get('votecount', 0)} votes)\n" + f"Link: https://vndb.org/{v['id']}" ) + await message.answer(text) - +# Обработчик персонажей @dp.message(Command("char")) -async def char(msg: types.Message): - cid = msg.text.replace("/char", "").strip() - if not cid: - return await msg.answer("No ID") - - data = await vndb.get_char(cid) - if not data: - return await msg.answer("Not found") - - await msg.answer( - f"{safe_text(data.get('name'))}\n" - f"Original: {safe_text(data.get('original'))}" - ) - - -@dp.message(Command("release")) -async def release(msg: types.Message): - rid = msg.text.replace("/release", "").strip() - if not rid: - return await msg.answer("No ID") - - data = await vndb.get_release(rid) - if not data: - return await msg.answer("Not found") - - await msg.answer( - f"{safe_text(data.get('title'))}\n" - f"Released: {safe_text(data.get('released'))}" - ) - - -async def main(): - await dp.start_polling(bot) - +async def search_char(message: types.Message): + query = message.extract_args() + if not query: return await message.answer("Пример: /char Kurisu") + + res = await fetch_vndb("character", ["search", "=", query], "id, name, original") + if not res: return await message.answer("❌ Персонаж не найден") + + out = [f"👤 Персонажи:"] + for c in res[:10]: + out.append(f"• {c['name']} ({c['id']})") + await message.answer("\n".join(out)) +# Запуск if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + if not TOKEN: + print("Error: TELEGRAM_TOKEN not set!") + else: + dp.run_polling(bot) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 9edcf17..50fc2dd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,14 @@ -version: "3.9" +version: '3.8' services: bot: build: . + container_name: vndb_bot restart: always env_file: - - .env \ No newline at end of file + - .env + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 698797a..5bf55f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -aiogram==3.6.0 -httpx==0.27.0 \ No newline at end of file +aiogram==3.10.0 +httpx==0.28.1 \ No newline at end of file