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 os
import logging import logging
import sqlite3 import sqlite3
import re
from pathlib import Path from pathlib import Path
import aiohttp import aiohttp
@ -70,8 +69,6 @@ _CONTEXT_SIZE = 20
_MAX_PROMPT_CHARS = 8_000 _MAX_PROMPT_CHARS = 8_000
# Путь к SQLite базе (берётся из переменной окружения или из config) # Путь к SQLite базе (берётся из переменной окружения или из config)
_DB_PATH = os.getenv("CHAT_HISTORY_DB_PATH", "/db/chat_history.sqlite3") _DB_PATH = os.getenv("CHAT_HISTORY_DB_PATH", "/db/chat_history.sqlite3")
_MAX_REPLY_SENTENCES = 3
_MAX_REPLY_CHARS = 280
def _init_db() -> None: def _init_db() -> None:
@ -142,49 +139,20 @@ def _build_messages(chat_id: int, system_prompt: str, current_text: str, current
return messages 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: async def _generate_response(chat_id: int, user_text: str, user_name: str) -> str:
# Формируем запрос к /v1/chat/completions с историей чата и возвращаем текст первого ответа. # Формируем запрос к /v1/chat/completions с историей чата и возвращаем текст первого ответа.
url = f"{LLAMA_API_URL.rstrip('/')}/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) msgs = await asyncio.to_thread(_build_messages, chat_id, SYSTEM_PROMPT, user_text, user_name)
payload = { payload = {
"messages": msgs, "messages": msgs,
"max_tokens": 90, "max_tokens": 200,
"temperature": 0.8, "temperature": 0.8,
"top_p": 0.9, "top_p": 0.9,
} }
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp: async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
data = await resp.json() data = await resp.json()
content = data["choices"][0]["message"]["content"].strip() return data["choices"][0]["message"]["content"].strip()
return _shorten_reply(content)
async def handle_talk(message: Message): 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) msgs = await asyncio.to_thread(_build_messages, chat_id, AUTOREPLY_SYSTEM_PROMPT, text, user_name)
payload = { payload = {
"messages": msgs, "messages": msgs,
"max_tokens": 70, "max_tokens": 150,
"temperature": 0.9, "temperature": 0.9,
"top_p": 0.9, "top_p": 0.9,
} }
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp: async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
data = await resp.json() data = await resp.json()
content = data["choices"][0]["message"]["content"].strip() return data["choices"][0]["message"]["content"].strip()
return _shorten_reply(content)
# Инициализация БД при импорте модуля # Инициализация БД при импорте модуля

View file

@ -3,14 +3,13 @@ from aiogram import types
import config import config
import logging import logging
import random import random
import re import json
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CAPTIONS = [ CAPTIONS = [
"Hot stuff 🔥", "Hot stuff 🔥",
"Sexy content 😏", "Sexy content 😏"
"ШПАЧИХА"
] ]
async def handle_nude_cmd(message: types.Message): async def handle_nude_cmd(message: types.Message):
@ -20,90 +19,66 @@ async def handle_nude_cmd(message: types.Message):
help_msg = ( help_msg = (
"🔥 <b>Справка по команде /nude:</b>\n\n" "🔥 <b>Справка по команде /nude:</b>\n\n"
"Получает случайные изображения взрослых женщин из открытых источников.\n" "Получает случайные изображения взрослых женщин из открытых источников.\n"
"• Использует прямое сканирование сайтов для поиска контента\n" "• Использует Pornhub API для поиска контента\n"
"• По умолчанию ищет популярные изображения\n" "• По умолчанию ищет популярные видео с высоким рейтингом\n"
"• Отправляет изображения напрямую\n\n" "• Отправляет превью видео как изображение\n\n"
"<i>Пример:</i> <code>/nude</code>\n" "<i>Пример:</i> <code>/nude</code>\n"
"<i>По умолчанию:</i> Случайные изображения с популярных сайтов" "<i>По умолчанию:</i> Популярный контент для взрослых"
) )
await message.reply(help_msg, parse_mode="HTML") await message.reply(help_msg, parse_mode="HTML")
return return
# Список сайтов для сканирования # Используем Pornhub API для поиска видео
sites = [ url = "https://www.pornhub.com/webmasters/search"
"https://www.imagefap.com/gallery.php?pg=random",
"https://xhamster.com/photos?sort=random",
"https://spankbang.com/random"
]
random_site = random.choice(sites) params = {
"category": "straight",
"ordering": "mostviewed",
"period": "week",
"thumbsize": "medium"
}
headers = { 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", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/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"
} }
try: try:
async with aiohttp.ClientSession() as session: 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: if resp.status != 200:
logger.error(f"Site access error: {resp.status}") logger.error(f"Pornhub API Error: {resp.status}")
await message.reply("Не удалось получить доступ к сайту :(") await message.reply("Не удалось получить контент от сервера :(")
return return
html = await resp.text() # Pornhub API возвращает HTML, нужно парсить
text = await resp.text()
# Ищем URL изображений в HTML # Ищем URL превью в HTML
img_patterns = [ import re
r'<img[^>]+src="([^"]+)"', thumb_pattern = r'<img[^>]+src="([^"]+)"[^>]+class="thumb"'
r'<a[^>]+href="([^"]+\.(jpg|jpeg|png|gif|webp))"', matches = re.findall(thumb_pattern, text)
r'data-src="([^"]+)"'
]
img_urls = [] if not matches:
for pattern in img_patterns: await message.reply("Не удалось найти контент :(")
matches = re.findall(pattern, html, re.IGNORECASE)
img_urls.extend(matches)
if not img_urls:
await message.reply("Изображения не найдены на странице :(")
return return
# Фильтруем и выбираем случайное изображение # Берем случайное превью
valid_imgs = [] thumb_url = random.choice(matches)
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
if any(ext in img_url.lower() for ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']): # Убедимся что URL полный
valid_imgs.append(img_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) caption = random.choice(CAPTIONS)
# Отправляем изображение # Отправляем как фото
try: try:
input_file = types.URLInputFile(final_img_url) input_file = types.URLInputFile(thumb_url)
await message.answer_photo(photo=input_file, caption=caption) await message.answer_photo(photo=input_file, caption=caption)
except Exception as e: except Exception as e:
logger.error(f"Error sending photo: {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: except Exception as e:
logger.exception("Error in nude command") 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 chat_id = message.chat.id
user_name = (message.from_user.first_name or "кент") if message.from_user else "кент" 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): if any(re.search(rf'\b{word}\b', text_lower) for word in config.KEYWORDS):
audio_path = config.BASE_DIR / "maket" / "Radiohead - Creep.mp3" audio_path = config.BASE_DIR / "maket" / "Radiohead - Creep.mp3"
if audio_path.exists(): 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) 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 import time as _time
now = _time.time() now = _time.time()
triggered = any(trigger in text_lower for trigger in config.AUTOREPLY_TRIGGERS) triggered = any(trigger in text_lower for trigger in config.AUTOREPLY_TRIGGERS)