forked from zovos/bot_tg
Compare commits
No commits in common. "3bad14eac2703a133f8be97677d9d90139a21711" and "361e86c384067b3ca50ac80c3b9d3e5cfde3679f" have entirely different histories.
3bad14eac2
...
361e86c384
4 changed files with 48 additions and 107 deletions
0
.codex
0
.codex
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
# Инициализация БД при импорте модуля
|
# Инициализация БД при импорте модуля
|
||||||
|
|
|
||||||
107
games/nude.py
107
games/nude.py
|
|
@ -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']):
|
|
||||||
valid_imgs.append(img_url)
|
|
||||||
|
|
||||||
if not valid_imgs:
|
# Убедимся что URL полный
|
||||||
await message.reply("Подходящие изображения не найдены :(")
|
if not thumb_url.startswith('http'):
|
||||||
return
|
thumb_url = f"https://thumb.pornhub.com{thumb_url}"
|
||||||
|
|
||||||
final_img_url = random.choice(valid_imgs)
|
caption = random.choice(CAPTIONS)
|
||||||
caption = random.choice(CAPTIONS)
|
|
||||||
|
# Отправляем как фото
|
||||||
# Отправляем изображение
|
try:
|
||||||
try:
|
input_file = types.URLInputFile(thumb_url)
|
||||||
input_file = types.URLInputFile(final_img_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{thumb_url}")
|
||||||
await message.answer(f"{caption}\n{final_img_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 :(")
|
||||||
|
|
|
||||||
7
main.py
7
main.py
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue