1
0
Fork 0
forked from zovos/bot_tg
bot_tg/games/uwu.py
Danil 3b29648a8c fix: критические баги, FurTok картинки, image proxy, gitignore
- Удалён дубликат handle_uwu_cmd в uwu.py
- Исправлен UnboundLocalError (delta/next_dt) в main.py
- FurTok: поддержка картинок при кастомном запросе
- Добавлен image proxy /api/proxy-image для e621
- CSS: абсолютное позиционирование медиа в furtok-card
- .gitignore: docker-compose.override.yaml
2026-04-14 22:49:49 +03:00

200 lines
7.6 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 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")
if not file_url:
raise Exception("У найденного поста нет прямого URL изображения :(")
caption = random.choice(CAPTIONS)
return {
"url": file_url,
"ext": ext,
"caption": caption
}
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 = (
"🐈 <b>Справка по тегам e621:</b>\n\n"
"Перечислите любые теги через пробел после команды.\n"
"• <b>rating</b>: <code>rating:safe</code> (безопасные), <code>rating:questionable</code>, <code>rating:explicit</code> (NSFW)\n"
"• <b>score</b>: <code>score:>500</code> (искать популярные посты с рейтингом больше 500)\n"
"• Исключить тег: поставьте минус, например <code>-fox</code>\n\n"
"<i>Пример:</i> <code>/uwu wolf -rating:explicit score:>100</code>\n"
"<i>По умолчанию:</i> <code>rating:safe score:>500 -animated</code>"
)
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"]
if ext in ("gif", "webm", "mp4"):
# Для анимаций: отправляем caption отдельным сообщением,
# а саму гифку/видео через URL напрямую — так Telegram
# корректно показывает её как анимацию, а не как файл
await message.answer(caption)
await message.answer_animation(animation=file_url)
elif ext in ("png", "jpg", "jpeg", "webp"):
input_file = types.URLInputFile(file_url)
await message.answer_photo(photo=input_file, caption=caption)
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 :(")