Compare commits

..

No commits in common. "3bad14eac2703a133f8be97677d9d90139a21711" and "361e86c384067b3ca50ac80c3b9d3e5cfde3679f" have entirely different histories.

4 changed files with 48 additions and 107 deletions

0
.codex
View file

View file

@ -2,7 +2,6 @@ import asyncio
import os
import logging
import sqlite3
import re
from pathlib import Path
import aiohttp
@ -70,8 +69,6 @@ _CONTEXT_SIZE = 20
_MAX_PROMPT_CHARS = 8_000
# Путь к SQLite базе (берётся из переменной окружения или из config)
_DB_PATH = os.getenv("CHAT_HISTORY_DB_PATH", "/db/chat_history.sqlite3")
_MAX_REPLY_SENTENCES = 3
_MAX_REPLY_CHARS = 280
def _init_db() -> None:
@ -142,49 +139,20 @@ def _build_messages(chat_id: int, system_prompt: str, current_text: str, current
return messages
def _shorten_reply(text: str, max_sentences: int = _MAX_REPLY_SENTENCES, max_chars: int = _MAX_REPLY_CHARS) -> str:
"""Ограничивает ответ до 1-3 предложений и разумной длины."""
cleaned = " ".join((text or "").replace("\n", " ").split()).strip()
if not cleaned:
return "Братуха, коротко: чёт пусто получилось."
# Делим по окончаниям предложений, сохраняя знаки.
parts = re.split(r"(?<=[.!?…])\s+", cleaned)
sentences = [p.strip() for p in parts if p.strip()]
if not sentences:
# На случай ответа без пунктуации.
words = cleaned.split()
return " ".join(words[:25]).strip()
short = " ".join(sentences[:max_sentences]).strip()
# Дополнительный предохранитель по символам.
if len(short) > max_chars:
clipped = short[:max_chars].rstrip()
last_break = max(clipped.rfind("."), clipped.rfind("!"), clipped.rfind("?"), clipped.rfind(""))
if last_break >= 40:
clipped = clipped[: last_break + 1].rstrip()
short = clipped
return short
async def _generate_response(chat_id: int, user_text: str, user_name: str) -> str:
# Формируем запрос к /v1/chat/completions с историей чата и возвращаем текст первого ответа.
url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
msgs = await asyncio.to_thread(_build_messages, chat_id, SYSTEM_PROMPT, user_text, user_name)
payload = {
"messages": msgs,
"max_tokens": 90,
"max_tokens": 200,
"temperature": 0.8,
"top_p": 0.9,
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
data = await resp.json()
content = data["choices"][0]["message"]["content"].strip()
return _shorten_reply(content)
return data["choices"][0]["message"]["content"].strip()
async def handle_talk(message: Message):
@ -262,15 +230,14 @@ async def generate_autoreply(chat_id: int, text: str, user_name: str) -> str:
msgs = await asyncio.to_thread(_build_messages, chat_id, AUTOREPLY_SYSTEM_PROMPT, text, user_name)
payload = {
"messages": msgs,
"max_tokens": 70,
"max_tokens": 150,
"temperature": 0.9,
"top_p": 0.9,
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
data = await resp.json()
content = data["choices"][0]["message"]["content"].strip()
return _shorten_reply(content)
return data["choices"][0]["message"]["content"].strip()
# Инициализация БД при импорте модуля

View file

@ -3,14 +3,13 @@ from aiogram import types
import config
import logging
import random
import re
import json
logger = logging.getLogger(__name__)
CAPTIONS = [
"Hot stuff 🔥",
"Sexy content 😏",
"ШПАЧИХА"
"Sexy content 😏"
]
async def handle_nude_cmd(message: types.Message):
@ -20,90 +19,66 @@ async def handle_nude_cmd(message: types.Message):
help_msg = (
"🔥 <b>Справка по команде /nude:</b>\n\n"
"Получает случайные изображения взрослых женщин из открытых источников.\n"
"• Использует прямое сканирование сайтов для поиска контента\n"
"• По умолчанию ищет популярные изображения\n"
"• Отправляет изображения напрямую\n\n"
"• Использует Pornhub API для поиска контента\n"
"• По умолчанию ищет популярные видео с высоким рейтингом\n"
"• Отправляет превью видео как изображение\n\n"
"<i>Пример:</i> <code>/nude</code>\n"
"<i>По умолчанию:</i> Случайные изображения с популярных сайтов"
"<i>По умолчанию:</i> Популярный контент для взрослых"
)
await message.reply(help_msg, parse_mode="HTML")
return
# Список сайтов для сканирования
sites = [
"https://www.imagefap.com/gallery.php?pg=random",
"https://xhamster.com/photos?sort=random",
"https://spankbang.com/random"
]
# Используем Pornhub API для поиска видео
url = "https://www.pornhub.com/webmasters/search"
random_site = random.choice(sites)
params = {
"category": "straight",
"ordering": "mostviewed",
"period": "week",
"thumbsize": "medium"
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1"
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
try:
async with aiohttp.ClientSession() as session:
async with session.get(random_site, headers=headers) as resp:
async with session.get(url, params=params, headers=headers) as resp:
if resp.status != 200:
logger.error(f"Site access error: {resp.status}")
await message.reply("Не удалось получить доступ к сайту :(")
logger.error(f"Pornhub API Error: {resp.status}")
await message.reply("Не удалось получить контент от сервера :(")
return
html = await resp.text()
# Pornhub API возвращает HTML, нужно парсить
text = await resp.text()
# Ищем URL изображений в HTML
img_patterns = [
r'<img[^>]+src="([^"]+)"',
r'<a[^>]+href="([^"]+\.(jpg|jpeg|png|gif|webp))"',
r'data-src="([^"]+)"'
]
# Ищем URL превью в HTML
import re
thumb_pattern = r'<img[^>]+src="([^"]+)"[^>]+class="thumb"'
matches = re.findall(thumb_pattern, text)
img_urls = []
for pattern in img_patterns:
matches = re.findall(pattern, html, re.IGNORECASE)
img_urls.extend(matches)
if not img_urls:
await message.reply("Изображения не найдены на странице :(")
if not matches:
await message.reply("Не удалось найти контент :(")
return
# Фильтруем и выбираем случайное изображение
valid_imgs = []
for img_url in img_urls:
if img_url.startswith('//'):
img_url = 'https:' + img_url
elif img_url.startswith('/'):
if 'imagefap.com' in random_site:
img_url = 'https://www.imagefap.com' + img_url
elif 'xhamster.com' in random_site:
img_url = 'https://xhamster.com' + img_url
elif 'spankbang.com' in random_site:
img_url = 'https://spankbang.com' + img_url
# Берем случайное превью
thumb_url = random.choice(matches)
if any(ext in img_url.lower() for ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']):
valid_imgs.append(img_url)
# Убедимся что URL полный
if not thumb_url.startswith('http'):
thumb_url = f"https://thumb.pornhub.com{thumb_url}"
if not valid_imgs:
await message.reply("Подходящие изображения не найдены :(")
return
final_img_url = random.choice(valid_imgs)
caption = random.choice(CAPTIONS)
# Отправляем изображение
# Отправляем как фото
try:
input_file = types.URLInputFile(final_img_url)
input_file = types.URLInputFile(thumb_url)
await message.answer_photo(photo=input_file, caption=caption)
except Exception as e:
logger.error(f"Error sending photo: {e}")
await message.answer(f"{caption}\n{final_img_url}")
await message.answer(f"{caption}\n{thumb_url}")
except Exception as e:
logger.exception("Error in nude command")
await message.reply("Произошла ошибка при обработке запроса :(")
await message.reply("Произошла ошибка при обращении к API :(")

View file

@ -1145,10 +1145,6 @@ async def handle_keywords(message: Message):
chat_id = message.chat.id
user_name = (message.from_user.first_name or "кент") if message.from_user else "кент"
# /ebalnik должен глушить все авто-реакции в чате, включая медиа/фото-ветки.
if chat_id in _autoreply_disabled_chats:
return
if any(re.search(rf'\b{word}\b', text_lower) for word in config.KEYWORDS):
audio_path = config.BASE_DIR / "maket" / "Radiohead - Creep.mp3"
if audio_path.exists():
@ -1175,6 +1171,9 @@ async def handle_keywords(message: Message):
# Записываем каждое обычное сообщение в историю чата
await asyncio.to_thread(push_message, chat_id, "user", user_name, message.text)
if chat_id in _autoreply_disabled_chats:
return
import time as _time
now = _time.time()
triggered = any(trigger in text_lower for trigger in config.AUTOREPLY_TRIGGERS)