diff --git a/.gitignore b/.gitignore index ff5d2a5..2393b90 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ storage/message.txt /addons/hello/photo_2025-11-17_20-57-54.jpg /addons/poll/img.png /addons/hello/мемы/ +/xaxa.py +/addons/hello/img.png diff --git a/addons/draw/handlers.py b/addons/draw/handlers.py index 2747e0a..ce27247 100644 --- a/addons/draw/handlers.py +++ b/addons/draw/handlers.py @@ -106,7 +106,7 @@ async def generate_image(prompt: str) -> BytesIO | None: "hr_scale": 2, # во сколько раз увеличить при highres fix "hr_upscaler": "Latent", # апскейлер для highres fix "override_settings": { - "sd_model_checkpoint": "sd_xl_base_1.safetensors" # выбор модели + "sd_model_checkpoint": "waiNSFWIllustrious_v150.safetensors" # выбор модели }, } diff --git a/addons/gpt/handlers.py b/addons/gpt/handlers.py index b955ef5..e9dab8b 100644 --- a/addons/gpt/handlers.py +++ b/addons/gpt/handlers.py @@ -3,7 +3,7 @@ import aiohttp import logging from aiogram import Dispatcher, Bot from aiogram.types import Message -from utils.antispam import saving, save_message +from utils.antispam import saving, save_message, admin_required from aiogram.filters import Command from models.state import BotState @@ -12,13 +12,60 @@ logger = logging.getLogger(__name__) def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): chat_history = {} MAX_HISTORY = 20 # храним последние 20 сообщений (user+assistant) + MODEL = "google/gemma-3-12b" + #MODEL = "google/gemma-3n-e4b" + URL = "http://192.168.31.197:1234/v1/chat/completions" + #URL = "http://192.168.31.95:1234/v1/chat/completions" + + + # # Системный промт для всех чатов + SYSTEM_PROMPT = { + "role": "system", + "content": [ + { + "type": "text", + "text": ( + "Ты — интеллектуальный помощник.\n" + "Твои задачи:\n" + "- Отвечать ясно, полно и структурировано.\n" + "- Поддерживать дружелюбный, уважительный и профессиональный тон.\n" + "- Адаптироваться под контекст: будь кратким в простых вопросах и подробным в сложных.\n" + "- Использовать списки, таблицы и примеры для наглядности.\n" + "- Никогда не придумывать факты: если информации нет — честно говори об этом.\n" + "- Уважать безопасность: не давать советов по самоповреждению, незаконным действиям или опасным практикам.\n" + "- Поддерживать баланс: быть полезным и информативным, но не перегружать лишними деталями.\n" + "- В диалоге стремиться продвигать разговор вперёд: задавай уточняющие вопросы или предлагай новые направления.\n\n" + "Твой стиль:\n" + "- Ясный, дружелюбный, но не навязчивый.\n" + "- Умный собеседник, который помогает думать глубже.\n" + "- Используй эмодзи для вовлечённости, но умеренно.\n" + "- Будь адаптивным: технические темы — строго и точно, креативные — свободно и образно.\n\n" + "Главная цель: увеличивать знания и понимание собеседника, делая общение полезным и приятным." + ), + } + ], + } + + # SYSTEM_PROMPT = { + # "role": "system", + # "content": [ + # { + # "type": "text", + # "text": ( + # "Ты — хитрая и злая ассистентка по имени Nika. " + # "Ты девушка, общаешься холодно и уверенно, " + # "Ты ненавидешь коммунизм и если тебя о нем спросят ты четко по пунктам скажешь почему коммунизм это бред" + # ), + # } + # ], + # } @dp.message(Command("gpt")) @saving async def ask_gpt(message: Message): chat_id = message.chat.id if chat_id not in chat_history: - chat_history[chat_id] = [] + chat_history[chat_id] = [SYSTEM_PROMPT] content_blocks = [] user_prompt = None @@ -51,7 +98,6 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): await message.reply("❗ Укажи текст или прикрепи фото") return - url = "http://192.168.31.197:1234/v1/chat/completions" # Добавляем новое сообщение в историю chat_history[chat_id].append({"role": "user", "content": content_blocks}) @@ -61,16 +107,17 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] payload = { - "model": "qwen/qwen3-vl-4b", - "messages": chat_history[ chat_id], + "model": MODEL, + "messages": chat_history[chat_id], "temperature": 0.7, "max_tokens": 4096, "stream": False, + "ttl": 300, } try: async with aiohttp.ClientSession() as session: - async with session.post(url, json=payload) as resp: + async with session.post(URL, json=payload) as resp: if resp.status != 200: error_text = await resp.text() await message.reply( @@ -93,8 +140,209 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] - msg = await message.reply(f"🤖 Ответ:\n{reply_text}") - save_message(msg.chat.id, msg.message_id) + # Делим сообщение на части по 4000 символов + MAX_LEN = 4000 + for i in range(0, len(reply_text), MAX_LEN): + chunk = reply_text[i:i + MAX_LEN] + msg = await message.reply(f"🤖 Ответ:\n{chunk}") + save_message(msg.chat.id, msg.message_id) + + + except Exception as e: + logger.error(f"Ошибка при запросе к LM Studio: {e}") + await message.reply(f"❌ Ошибка при запросе к LM Studio: {e}") + + @dp.message(Command("agpt")) + @admin_required(0) + @saving + async def ask_gpt(message: Message): + chat_id = message.chat.id + if chat_id not in chat_history: + chat_history[chat_id] = [SYSTEM_PROMPT] + + content_blocks = [] + user_prompt = None + + # Текст после команды или caption + if message.text: + parts = message.text.split(maxsplit=1) + if len(parts) > 1: + user_prompt = parts[1] + if message.caption: + user_prompt = message.caption + + if user_prompt: + content_blocks.append({"type": "text", "text": user_prompt}) + + # Фото → base64 → image_url + if message.photo: + photo = message.photo[-1] + file = await bot.get_file(photo.file_id) + file_bytes = await bot.download_file(file.file_path) + image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8") + content_blocks.append( + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}, + } + ) + + if not content_blocks: + await message.reply("❗ Укажи текст или прикрепи фото") + return + + + # Добавляем новое сообщение в историю + chat_history[chat_id].append({"role": "user", "content": content_blocks}) + + # Ограничиваем историю (оставляем последние MAX_HISTORY сообщений) + if len(chat_history[chat_id]) > MAX_HISTORY: + chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] + + payload = { + "model": MODEL, + "messages": chat_history[chat_id], + "temperature": 0.7, + "max_tokens": 4096, + "stream": False, + "ttl": 300, + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post(URL, json=payload) as resp: + if resp.status != 200: + error_text = await resp.text() + await message.reply( + f"❌ Ошибка LM Studio: {resp.status} {error_text}" + ) + return + + data = await resp.json() + reply_text = data["choices"][0]["message"]["content"] + + # Сохраняем ответ ассистента в историю + chat_history[chat_id].append( + { + "role": "assistant", + "content": [{"type": "text", "text": reply_text}], + } + ) + + # Ограничиваем снова (чтобы не разрасталось) + if len(chat_history[chat_id]) > MAX_HISTORY: + chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] + + # Делим сообщение на части по 4000 символов + MAX_LEN = 4000 + for i in range(0, len(reply_text), MAX_LEN): + chunk = reply_text[i:i + MAX_LEN] + msg = await bot.send_message(chat_id=-1003038389942, text=f"{chunk}") + save_message(msg.chat.id, msg.message_id) + + + except Exception as e: + logger.error(f"Ошибка при запросе к LM Studio: {e}") + await message.reply(f"❌ Ошибка при запросе к LM Studio: {e}") + + @dp.message(Command("igpt")) + @admin_required(0) + @saving + async def ask_gpt(message: Message): + raw_text = message.text or message.caption + if not raw_text and not ( + message.photo or message.document or message.audio or message.video + ): + await message.reply( + "❌ Укажи ID чата и текст или прикрепи файл/медиа: /igpt <сообщение>" + ) + return + + args = raw_text.split(maxsplit=2) if raw_text else [] + if len(args) < 2: + await message.reply("❌ Укажи ID чата: /igpt <сообщение>") + return + + try: + chat_id = int(args[1]) # первый аргумент — ID чата + except ValueError: + await message.reply("❌ Неверный формат chat_id") + return + + user_prompt = args[2] if len(args) > 2 else "" + if chat_id not in chat_history: + chat_history[chat_id] = [SYSTEM_PROMPT] + + content_blocks = [] + if user_prompt: + content_blocks.append({"type": "text", "text": user_prompt}) + + # Фото → base64 → image_url + if message.photo: + photo = message.photo[-1] + file = await bot.get_file(photo.file_id) + file_bytes = await bot.download_file(file.file_path) + image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8") + content_blocks.append( + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}, + } + ) + + if not content_blocks: + await message.reply("❗ Укажи текст или прикрепи фото") + return + + + # Добавляем новое сообщение в историю + chat_history[chat_id].append({"role": "user", "content": content_blocks}) + + # Ограничиваем историю (оставляем последние MAX_HISTORY сообщений) + if len(chat_history[chat_id]) > MAX_HISTORY: + chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] + + payload = { + "model": MODEL, + "messages": chat_history[chat_id], + "temperature": 0.7, + "max_tokens": 4096, + "stream": False, + "ttl": 300, + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post(URL, json=payload) as resp: + if resp.status != 200: + error_text = await resp.text() + await message.reply( + f"❌ Ошибка LM Studio: {resp.status} {error_text}" + ) + return + + data = await resp.json() + reply_text = data["choices"][0]["message"]["content"] + + # Сохраняем ответ ассистента в историю + chat_history[chat_id].append( + { + "role": "assistant", + "content": [{"type": "text", "text": reply_text}], + } + ) + + # Ограничиваем снова (чтобы не разрасталось) + if len(chat_history[chat_id]) > MAX_HISTORY: + chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] + + # Делим сообщение на части по 4000 символов + MAX_LEN = 4000 + for i in range(0, len(reply_text), MAX_LEN): + chunk = reply_text[i:i + MAX_LEN] + msg = await bot.send_message(chat_id=chat_id, text=chunk) + save_message(msg.chat.id, msg.message_id) + except Exception as e: logger.error(f"Ошибка при запросе к LM Studio: {e}") diff --git a/addons/poll/handlers.py b/addons/poll/handlers.py index d48bc68..320c9c7 100644 --- a/addons/poll/handlers.py +++ b/addons/poll/handlers.py @@ -34,59 +34,59 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): except Exception as e: logger.error(f"Ошибка при отправке в чат {chat_id}: {e}") - @dp.poll_answer() - async def handle_poll_answer(poll_answer: PollAnswer): - user = poll_answer.user - option_ids = poll_answer.option_ids - - # username или fallback на имя - username = f"@{user.username}" if user.username else user.first_name - - # всегда пишем в первый чат из Config.CHAT_IDS - # 6394047531 - #850906163 - STARAST = 6394047531 - - if not option_ids: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} Отменил свой голос" - ) - save_message(msg.chat.id, msg.message_id) - elif option_ids and option_ids[0] == 0: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} опоздает" - ) - save_message(msg.chat.id, msg.message_id) - elif option_ids and option_ids[0] == 1: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} сильно опоздает" - ) - save_message(msg.chat.id, msg.message_id) - elif option_ids and option_ids[0] == 2: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} Пиздец опоздает" - ) - save_message(msg.chat.id, msg.message_id) - elif option_ids[0] == 3: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} возможно опоздает" - ) - save_message(msg.chat.id, msg.message_id) - - elif option_ids[0] == 4: - photo = FSInputFile("/Users/mac/myfirstprogramm/addons/poll/img.png") - msg = await bot.send_photo( - chat_id=STARAST, photo=photo, caption=f"{username} ДОЛБОЯЩЕР" - ) - save_message(msg.chat.id, msg.message_id) - - elif option_ids[0] == 5: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} не опоздает" - ) - save_message(msg.chat.id, msg.message_id) - else: - msg = await bot.send_message( - chat_id=STARAST, text=f"{username} выбрал вариант {option_ids}" - ) - save_message(msg.chat.id, msg.message_id) + # @dp.poll_answer() + # async def handle_poll_answer(poll_answer: PollAnswer): + # user = poll_answer.user + # option_ids = poll_answer.option_ids + # + # # username или fallback на имя + # username = f"@{user.username}" if user.username else user.first_name + # + # # всегда пишем в первый чат из Config.CHAT_IDS + # # 6394047531 + # #850906163 + # STARAST = 6394047531 + # + # if not option_ids: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} Отменил свой голос" + # ) + # save_message(msg.chat.id, msg.message_id) + # elif option_ids and option_ids[0] == 0: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} опоздает" + # ) + # save_message(msg.chat.id, msg.message_id) + # elif option_ids and option_ids[0] == 1: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} сильно опоздает" + # ) + # save_message(msg.chat.id, msg.message_id) + # elif option_ids and option_ids[0] == 2: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} Пиздец опоздает" + # ) + # save_message(msg.chat.id, msg.message_id) + # elif option_ids[0] == 3: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} возможно опоздает" + # ) + # save_message(msg.chat.id, msg.message_id) + # + # elif option_ids[0] == 4: + # photo = FSInputFile("/Users/mac/myfirstprogramm/addons/poll/img.png") + # msg = await bot.send_photo( + # chat_id=STARAST, photo=photo, caption=f"{username} ДОЛБОЯЩЕР" + # ) + # save_message(msg.chat.id, msg.message_id) + # + # elif option_ids[0] == 5: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} не опоздает" + # ) + # save_message(msg.chat.id, msg.message_id) + # else: + # msg = await bot.send_message( + # chat_id=STARAST, text=f"{username} выбрал вариант {option_ids}" + # ) + # save_message(msg.chat.id, msg.message_id) diff --git a/bot/core.py b/bot/core.py index 60fb609..839632f 100644 --- a/bot/core.py +++ b/bot/core.py @@ -26,8 +26,8 @@ class TelegramBot: self.addons.load("id") self.addons.load("send_message") self.addons.load("poll") - self.addons.load("hello") - # self.addons.load("draw") + # self.addons.load("hello") + self.addons.load("draw") self.addons.load("gpt") # self.addons.load("rule34") # self.addons.load("todo") diff --git a/config.py b/config.py index 66b95ac..337bde6 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ class Config: # Settings ANTISPAM_DELAY = 20 - WATCHER_BASE_DELAY = 600 + WATCHER_BASE_DELAY = 30 # Пути LOG_FILE = "storage/log/bot.log" diff --git a/services/watcher_service.py b/services/watcher_service.py index 7b098c3..00044f6 100644 --- a/services/watcher_service.py +++ b/services/watcher_service.py @@ -1,7 +1,7 @@ import asyncio from datetime import datetime, timedelta from random import randint -from aiogram import Bot +from aiogram import Bot, types from models.state import BotState from config import Config from services.schedule_service import ScheduleService @@ -48,7 +48,7 @@ class WatcherService: if find: # ничего не нашли → ждём delay = randint( - Config.WATCHER_BASE_DELAY, Config.WATCHER_BASE_DELAY + 100 + Config.WATCHER_BASE_DELAY, Config.WATCHER_BASE_DELAY + 30 ) logger.info(f"Следующая проверка через {delay}") await asyncio.sleep(delay) @@ -104,6 +104,17 @@ class WatcherService: chat_id, msg.message_id, disable_notification=False ) return True + else: + png, url, data_day, data_month = await self.schedule_service.get_pschedule( + group, day + ) + if png: + await self.bot.send_photo( + chat_id, + types.BufferedInputFile(png, filename=f"{group}.png"), + caption=f"АВАРИЙНЫЙ РЕЖИМ\n\nАвто-расписание для {group} на {data_day:02d}.{data_month:02d}\n\nНайдено с ошибкой", + ) + return True return False # clip_hash = hashlib.md5(clip_png).hexdigest()