forked from zovos/bot_tg
160 lines
5.4 KiB
Python
160 lines
5.4 KiB
Python
import logging
|
||
import random
|
||
from pathlib import PurePosixPath
|
||
from urllib.parse import urlparse
|
||
|
||
import aiohttp
|
||
from aiogram import types
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
CAPTIONS = [
|
||
"Шо ты блять псюнчик решил свой подергать?",
|
||
"Решил подрочить сука?",
|
||
"гнойный писюн у тебя кнч, да",
|
||
]
|
||
|
||
LESAINT_POSTS_API = "https://lesaintdesseins.fr/wp-json/wp/v2/posts"
|
||
LESAINT_REFERER = "https://lesaintdesseins.fr/"
|
||
LATEST_POSTS_LIMIT = 30
|
||
REQUEST_HEADERS = {
|
||
"User-Agent": (
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||
"Chrome/124.0.0.0 Safari/537.36"
|
||
),
|
||
"Accept": "application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||
"Accept-Language": "en-US,en;q=0.9",
|
||
"Referer": LESAINT_REFERER,
|
||
}
|
||
|
||
|
||
def _extract_photo_candidates(posts: list[dict]) -> list[dict]:
|
||
candidates = []
|
||
|
||
for post in posts:
|
||
embedded = post.get("_embedded") or {}
|
||
media_items = embedded.get("wp:featuredmedia") or []
|
||
|
||
for media in media_items:
|
||
if media.get("media_type") != "image":
|
||
continue
|
||
|
||
media_details = media.get("media_details") or {}
|
||
sizes = media_details.get("sizes") or {}
|
||
image_url = (
|
||
(sizes.get("full") or {}).get("source_url")
|
||
or media.get("source_url")
|
||
)
|
||
|
||
if not image_url:
|
||
continue
|
||
|
||
candidates.append(
|
||
{
|
||
"image_url": image_url,
|
||
"post_url": post.get("link", LESAINT_REFERER),
|
||
"title": (post.get("title") or {}).get("rendered", "").strip(),
|
||
}
|
||
)
|
||
break
|
||
|
||
return candidates
|
||
|
||
|
||
def _guess_filename(image_url: str) -> str:
|
||
parsed = urlparse(image_url)
|
||
filename = PurePosixPath(parsed.path).name or "nude.jpg"
|
||
|
||
if "." not in filename:
|
||
filename = f"{filename}.jpg"
|
||
|
||
return filename
|
||
|
||
|
||
async def _fetch_latest_photo_candidates(session: aiohttp.ClientSession) -> list[dict]:
|
||
params = {
|
||
"per_page": str(LATEST_POSTS_LIMIT),
|
||
"orderby": "date",
|
||
"order": "desc",
|
||
"_embed": "wp:featuredmedia",
|
||
}
|
||
|
||
async with session.get(LESAINT_POSTS_API, params=params, headers=REQUEST_HEADERS) as resp:
|
||
if resp.status != 200:
|
||
body = await resp.text()
|
||
raise RuntimeError(f"API returned {resp.status}: {body[:300]}")
|
||
|
||
posts = await resp.json(content_type=None)
|
||
|
||
if not isinstance(posts, list):
|
||
raise RuntimeError("Unexpected API response format")
|
||
|
||
return _extract_photo_candidates(posts)
|
||
|
||
|
||
async def _download_photo(
|
||
session: aiohttp.ClientSession, image_url: str
|
||
) -> tuple[bytes | None, str]:
|
||
async with session.get(image_url, headers=REQUEST_HEADERS) as resp:
|
||
if resp.status != 200:
|
||
logger.warning("Image download failed for %s: %s", image_url, resp.status)
|
||
return None, _guess_filename(image_url)
|
||
|
||
content_type = resp.headers.get("Content-Type", "")
|
||
if "image" not in content_type.lower():
|
||
logger.warning(
|
||
"Unexpected content type for %s: %s", image_url, content_type
|
||
)
|
||
return None, _guess_filename(image_url)
|
||
|
||
return await resp.read(), _guess_filename(image_url)
|
||
|
||
|
||
async def handle_nude_cmd(message: types.Message):
|
||
args = message.text.split(maxsplit=1)
|
||
|
||
if len(args) > 1 and args[1].strip() == "-h":
|
||
help_msg = (
|
||
"🔥 <b>Справка по команде /nude:</b>\n\n"
|
||
"Берет случайную фотку из последних постов сайта le saint des seins.\n"
|
||
"• Источник: <code>https://lesaintdesseins.fr/</code>\n"
|
||
"• Достает последние посты через WordPress REST API\n"
|
||
"• Выбирает случайную картинку и отправляет ее прямо в Telegram\n\n"
|
||
"<i>Пример:</i> <code>/nude</code>"
|
||
)
|
||
await message.reply(help_msg, parse_mode="HTML")
|
||
return
|
||
|
||
timeout = aiohttp.ClientTimeout(total=30)
|
||
|
||
try:
|
||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||
candidates = await _fetch_latest_photo_candidates(session)
|
||
|
||
if not candidates:
|
||
await message.reply("Не удалось найти свежие фотки :(")
|
||
return
|
||
|
||
selected = random.choice(candidates)
|
||
caption = random.choice(CAPTIONS)
|
||
image_bytes, filename = await _download_photo(session, selected["image_url"])
|
||
|
||
if image_bytes:
|
||
photo = types.BufferedInputFile(image_bytes, filename=filename)
|
||
await message.answer_photo(photo=photo, caption=caption)
|
||
return
|
||
|
||
try:
|
||
photo = types.URLInputFile(selected["image_url"])
|
||
await message.answer_photo(photo=photo, caption=caption)
|
||
except Exception:
|
||
logger.exception("Failed to send photo by URL")
|
||
await message.answer(
|
||
f"{caption}\n{selected['image_url']}\n{selected['post_url']}"
|
||
)
|
||
|
||
except Exception:
|
||
logger.exception("Error in nude command")
|
||
await message.reply("Произошла ошибка при получении фото :(")
|