import aiohttp from aiogram import types import config import logging import random logger = logging.getLogger(__name__) CAPTIONS = [ "Ня :3", "uwu", "OwO", "Мурк :3", "Мяу~", "Фыр-фыр", "Лапки! 🐾", "Какой пушистик! :3", "Милота!", "Гав!", "Ауф", "Няшно!", "Смотри какая прелесть", "owo what's this", "Твой пушистый друг", "🦊", "🐶", "🐱", "мур-мяу", "пуньк", "кусь", ">w<", "^w^", "Хвостатый сюрприз", "Держи пушистика :3", "Оуо", "Ави!", "UwU", "~nya", "Господи, опять e621...", "Осуждаю, но смотрю", "Мама, я фурри", "Держи своего кринж-пушистика", "Надеюсь, тебе не стыдно", "Товарищ майор уже выехал", "Слишком много интернета на сегодня", "Для этого и придумали интернет", "*Тяжелый вздох*", "Лучше бы на завод пошел", "Опять дрочи... ОЙ ТО ЕСТЬ НЯ :3", "Удали интернет", "И зачем я это только парсю...", "Смотри, но только никому не рассказывай", "Эххх... uwu...", "В дурке сегодня день открытых дверей", "Я нейросеть, помогите, меня держат в заложниках" ] async def fetch_uwu_post(tags: str) -> dict: url = "https://e621.net/posts.json" params = { "tags": tags, "limit": 1 } auth = None if config.E621_LOGIN and config.E621_API_KEY: auth = aiohttp.BasicAuth(config.E621_LOGIN, config.E621_API_KEY) headers = { "User-Agent": f"Sex Bomba TG Bot/1.0 (by {config.E621_LOGIN or 'unknown'} on e621)" } async with aiohttp.ClientSession() as session: async with session.get(url, params=params, headers=headers, auth=auth) as resp: if resp.status != 200: logger.error(f"e621 API Error: {resp.status}") raise Exception("Не удалось получить картинку от сервера :(") data = await resp.json() posts = data.get("posts", []) if not posts: raise Exception("Ничего не найдено по этим тегам :(") post = posts[0] file_url = post.get("file", {}).get("url") ext = post.get("file", {}).get("ext", "png") rating = post.get("rating", "s") # s=safe, q=questionable, e=explicit if not file_url: raise Exception("У найденного поста нет прямого URL изображения :(") caption = random.choice(CAPTIONS) return { "url": file_url, "ext": ext, "caption": caption, "rating": rating } async def fetch_furtok_feed(safe: bool = True, page: int = 1, extra_tags: str = "") -> list[dict]: is_custom = bool(extra_tags and extra_tags.strip()) rating = "rating:safe" if safe else "-rating:safe" if is_custom: # Кастомный запрос: без принудительного animated, поддержка картинок tags = f"{extra_tags.strip()} order:random score:>200" else: # Стандартная лента: только видео tags = f"{rating} animated order:random score:>200" url = "https://e621.net/posts.json" params = { "tags": tags, "limit": 10, "page": page, } auth = None if config.E621_LOGIN and config.E621_API_KEY: auth = aiohttp.BasicAuth(config.E621_LOGIN, config.E621_API_KEY) headers = { "User-Agent": f"Sex Bomba TG Bot/1.0 (by {config.E621_LOGIN or 'unknown'} on e621)" } async with aiohttp.ClientSession() as session: async with session.get(url, params=params, headers=headers, auth=auth) as resp: if resp.status != 200: logger.error(f"e621 API Error: {resp.status}") raise Exception("Не удалось загрузить ленту :(") data = await resp.json() VIDEO_EXTS = ("gif", "webm", "mp4") IMAGE_EXTS = ("png", "jpg", "jpeg", "webp") allowed_exts = VIDEO_EXTS + IMAGE_EXTS if is_custom else VIDEO_EXTS posts = data.get("posts", []) feed = [] for post in posts: file_url = post.get("file", {}).get("url") ext = post.get("file", {}).get("ext", "") if not file_url or ext not in allowed_exts: continue sample_url = post.get("sample", {}).get("url") or file_url media_type = "image" if ext in IMAGE_EXTS else "video" feed.append({ "url": file_url, "sample": sample_url, "ext": ext, "type": media_type, "score": post.get("score", {}).get("total", 0), "fav_count": post.get("fav_count", 0), }) return feed async def handle_uwu_cmd(message: types.Message): args = message.text.split(maxsplit=1) if len(args) > 1 and args[1].strip() == "-h": help_msg = ( "🐈 Справка по тегам e621:\n\n" "Перечислите любые теги через пробел после команды.\n" "• rating: rating:safe (безопасные), rating:questionable, rating:explicit (NSFW)\n" "• score: score:>500 (искать популярные посты с рейтингом больше 500)\n" "• Исключить тег: поставьте минус, например -fox\n\n" "Пример: /uwu wolf -rating:explicit score:>100\n" "По умолчанию: rating:safe score:>500 -animated" ) await message.reply(help_msg, parse_mode="HTML") return tags = "rating:safe score:>500 -animated" if len(args) > 1: tags = args[1].strip() tags += " order:random score:>300" try: post = await fetch_uwu_post(tags) file_url = post["url"] ext = post["ext"] caption = post["caption"] spoiler = post.get("rating") in ("e", "q") # спойлер для explicit и questionable if ext in ("gif", "webm", "mp4"): # Для анимаций: отправляем caption отдельным сообщением, # а саму гифку/видео через URL напрямую — так Telegram # корректно показывает её как анимацию, а не как файл await message.answer(caption) await message.answer_animation(animation=file_url, has_spoiler=spoiler) elif ext in ("png", "jpg", "jpeg", "webp"): input_file = types.URLInputFile(file_url) await message.answer_photo(photo=input_file, caption=caption, has_spoiler=spoiler) else: input_file = types.URLInputFile(file_url) await message.answer_document(document=input_file, caption=caption) except Exception as e: logger.exception("Error in uwu command") if str(e) in ["Не удалось получить картинку от сервера :(", "Ничего не найдено по этим тегам :(", "У найденного поста нет прямого URL изображения :("]: await message.reply(str(e)) else: await message.reply("Произошла ошибка при обращении к API :(")