diff --git a/.gitignore b/.gitignore index 1a0db67..d80e779 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ utils/__pycache__/ storage/message.txt /addons/dowloadmp4_to_youtube/gettoken.py /drevo.py +/addons/example_addon/cookies.txt +/storage/message.db diff --git a/addons/example_addon/dowloadmp3_to_youtube.py b/addons/example_addon/dowloadmp3_to_youtube.py index eb5abc3..00d8b2d 100644 --- a/addons/example_addon/dowloadmp3_to_youtube.py +++ b/addons/example_addon/dowloadmp3_to_youtube.py @@ -19,6 +19,7 @@ async def get_video_info(url: str) -> dict: 'yt-dlp', '--dump-json', '--no-playlist', + '--cookies', '~/myfirstprogramm/addons/example_addon/cookies.txt', url, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE @@ -108,6 +109,7 @@ async def download_mp3_isolated(url: str) -> tuple[str, dict]: process = await asyncio.create_subprocess_exec( 'yt-dlp', '-x', '--audio-format', 'mp3', + '--cookies', '~/myfirstprogramm/addons/example_addon/cookies.txt', '--audio-quality', '320K', '--no-playlist', '-o', output_template, diff --git a/addons/hello/__init__.py b/addons/hello/__init__.py new file mode 100644 index 0000000..8a3fbb5 --- /dev/null +++ b/addons/hello/__init__.py @@ -0,0 +1,7 @@ +def register(dp, state, bot): + from . import handlers + handlers.register_handlers(dp, state, bot) + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/hello/handlers.py b/addons/hello/handlers.py new file mode 100644 index 0000000..cb4150d --- /dev/null +++ b/addons/hello/handlers.py @@ -0,0 +1,35 @@ +from aiogram import Dispatcher, Bot +from aiogram.types import Message +from aiogram.filters import Command +from models.state import BotState +from config import Config +import logging +from utils.antispam import admin_required +from storage.message_storage import save_message # импортируем функцию + +logger = logging.getLogger(__name__) + +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): + @dp.message(Command("hello")) + @admin_required(1) + async def hello(message: Message): + # сохраняем саму команду пользователя + save_message(message.chat.id, message.message_id) + + for admin_id in Config.ADMINS: + try: + name = Config.Names.get(admin_id, "Админ") + msg = await bot.send_message( + chat_id=admin_id, + text=f"🤖 Я готов к работе, господин {name}!" + ) + # сохраняем сообщение, отправленное админу + save_message(msg.chat.id, msg.message_id) + + logger.info(f"Сообщение отправлено админу {admin_id} ({name})") + except Exception as e: + logger.error(f"Ошибка при отправке админу {admin_id}: {e}") + + confirm_msg = await message.answer("✅ Всем админам отправлено приветствие.") + # сохраняем подтверждение пользователю + save_message(confirm_msg.chat.id, confirm_msg.message_id) diff --git a/addons/id/handlers.py b/addons/id/handlers.py index 4138088..5c297dc 100644 --- a/addons/id/handlers.py +++ b/addons/id/handlers.py @@ -3,11 +3,15 @@ from aiogram import Dispatcher, Bot from aiogram.types import Message from aiogram.filters import Command from models.state import BotState +from utils.antispam import saving + +API_URL = "http://127.0.0.1:7700/speak" logger = getLogger(__name__) def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): @dp.message(Command("id")) + @saving async def id(message: Message): id = message.from_user.id - await message.reply(str(id)) + msg = await message.reply(str(id)) \ No newline at end of file diff --git a/addons/poll/__init__.py b/addons/poll/__init__.py new file mode 100644 index 0000000..8a3fbb5 --- /dev/null +++ b/addons/poll/__init__.py @@ -0,0 +1,7 @@ +def register(dp, state, bot): + from . import handlers + handlers.register_handlers(dp, state, bot) + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/poll/handlers.py b/addons/poll/handlers.py new file mode 100644 index 0000000..09c87b9 --- /dev/null +++ b/addons/poll/handlers.py @@ -0,0 +1,79 @@ +from config import Config +import aiohttp +from aiogram.types import BufferedInputFile +from utils.antispam import admin_required +from aiogram import Dispatcher, Bot +from aiogram.types import Message +from models.state import BotState +from aiogram.filters import Command +from logging import getLogger +from aiogram.types import PollAnswer +from storage.message_storage import save_message + +logger = getLogger(__name__) +API_URL = "http://127.0.0.1:7700/speak" + +from config import Config +import aiohttp +from aiogram.types import BufferedInputFile +from utils.antispam import admin_required +from aiogram import Dispatcher, Bot +from aiogram.types import Message +from models.state import BotState +from aiogram.filters import Command +from logging import getLogger +from aiogram.types import PollAnswer +from storage.message_storage import save_message + +logger = getLogger(__name__) +API_URL = "http://127.0.0.1:7700/speak" + +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): + @dp.message(Command("poll")) + @admin_required(5) + async def send_poll(message: Message): + for chat_id in Config.CHAT_IDS: + try: + poll_msg = await bot.send_poll( + chat_id=chat_id, + question="Кто опоздает?", + options=["Я", "Не знаю", "Наверное"], + is_anonymous=False, + allows_multiple_answers=False + ) + # сохраняем сам опрос + save_message(poll_msg.chat.id, poll_msg.message_id) + logger.info(f"Опрос отправлен в чат {chat_id}") + + except Exception as e: + logger.error(f"Ошибка при отправке в чат {chat_id}: {e}") + + confirm_msg = await message.answer("✅ Сообщение отправлено.") + save_message(confirm_msg.chat.id, confirm_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 + + if option_ids and option_ids[0] == 0: + msg = await bot.send_message( + chat_id=6394047531, + text=f"{username} опоздает" + ) + save_message(msg.chat.id, msg.message_id) + else: + msg = await bot.send_message( + chat_id=6394047531, + text=f"{username} выбрал вариант {option_ids}" + ) + save_message(msg.chat.id, msg.message_id) + + + + diff --git a/addons/send_message/__init__.py b/addons/send_message/__init__.py new file mode 100644 index 0000000..8a3fbb5 --- /dev/null +++ b/addons/send_message/__init__.py @@ -0,0 +1,7 @@ +def register(dp, state, bot): + from . import handlers + handlers.register_handlers(dp, state, bot) + +def unregister(dp): + # Здесь можно удалить хендлеры, если нужно + dp.message_handlers.handlers.clear() diff --git a/addons/send_message/handlers.py b/addons/send_message/handlers.py new file mode 100644 index 0000000..494d6f0 --- /dev/null +++ b/addons/send_message/handlers.py @@ -0,0 +1,133 @@ +from config import Config +import aiohttp +from aiogram.types import BufferedInputFile +from utils.antispam import admin_required +from aiogram import Dispatcher, Bot +from aiogram.types import Message +from models.state import BotState +from aiogram.filters import Command +from logging import getLogger + +logger = getLogger(__name__) +API_URL = "http://127.0.0.1:7700/speak" + +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): + @dp.message(Command("vadmin")) + @admin_required(0) + async def vadmin(message: Message): + parts = message.text.split(maxsplit=1) + if len(parts) < 2: + await message.reply("❗ Укажи текст после /vadmin") + return + phrase = parts[1] + + # Запрос к TTS API + async with aiohttp.ClientSession() as session: + async with session.post(API_URL, json={"text": phrase}) as resp: + if resp.status != 200: + await message.reply("Ошибка генерации аудио") + return + audio_bytes = await resp.read() + audio_file = BufferedInputFile(audio_bytes, filename="speech.wav") + + # Рассылка в чаты + for chat_id in Config.CHAT_IDS: + try: + await bot.send_audio(chat_id, audio=audio_file, caption="🎙 Текст") + except Exception as e: + await message.reply(f"Не удалось отправить в {chat_id}: {e}") + + await message.reply("✅ Озвучка разослана.") + + @dp.message(Command("admin")) + @admin_required(0) + async def admin(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("❌ Укажи текст или прикрепи файл/медиа: /admin <сообщение>") + return + + # Отрезаем саму команду (/admin) + args = raw_text.split(maxsplit=1) if raw_text else [] + text_to_send = args[1] if len(args) > 1 else "" + + for chat_id in Config.CHAT_IDS: + try: + if message.photo: + # Фото + photo = message.photo[-1].file_id + await bot.send_photo(chat_id, photo, caption=text_to_send) + + elif message.document: + # Документ + await bot.send_document(chat_id, message.document.file_id, caption=text_to_send) + + elif message.audio: + # Аудио (музыка) + await bot.send_audio(chat_id, message.audio.file_id, caption=text_to_send) + + elif message.video: + # Видео + await bot.send_video(chat_id, message.video.file_id, caption=text_to_send) + + else: + # Только текст + await bot.send_message(chat_id, text_to_send) + + logger.info(f"Сообщение отправлено в чат {chat_id}") + + except Exception as e: + logger.error(f"Ошибка при отправке в чат {chat_id}: {e}") + + await message.answer("✅ Сообщение отправлено.") + + @dp.message(Command("iadmin")) + @admin_required(0) + async def id_admin(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 чата и текст или прикрепи файл/медиа: /iadmin <сообщение>") + return + + # Отрезаем саму команду (/iadmin) + args = raw_text.split(maxsplit=2) if raw_text else [] + if len(args) < 2: + await message.reply("❌ Укажи ID чата: /iadmin <сообщение>") + return + + try: + chat_id = int(args[1]) # первый аргумент — ID чата + except ValueError: + await message.reply("❌ Неверный формат chat_id") + return + + text_to_send = args[2] if len(args) > 2 else "" + + try: + if message.photo: + # Фото + photo = message.photo[-1].file_id + await bot.send_photo(chat_id, photo, caption=text_to_send) + + elif message.document: + # Документ + await bot.send_document(chat_id, message.document.file_id, caption=text_to_send) + + elif message.audio: + # Аудио (музыка) + await bot.send_audio(chat_id, message.audio.file_id, caption=text_to_send) + + elif message.video: + # Видео + await bot.send_video(chat_id, message.video.file_id, caption=text_to_send) + + else: + # Только текст + await bot.send_message(chat_id, text_to_send) + + logger.info(f"Сообщение отправлено в чат {chat_id}") + await message.answer("✅ Сообщение отправлено.") + + except Exception as e: + logger.error(f"Ошибка при отправке в чат {chat_id}: {e}") + await message.answer(f"❌ Ошибка при отправке: {e}") diff --git a/bot/core.py b/bot/core.py index 4c148cc..57111b3 100644 --- a/bot/core.py +++ b/bot/core.py @@ -24,6 +24,9 @@ class TelegramBot: #add addons self.addons.load("example_addon") self.addons.load("id") + self.addons.load("send_message") + self.addons.load("poll") + self.addons.load("hello") async def start(self): """Запуск бота""" diff --git a/config.py b/config.py index 8864d54..3a02bc3 100644 --- a/config.py +++ b/config.py @@ -15,7 +15,12 @@ class Config: # Admins (user_id: уровень) ADMINS: Dict[int, int] = { 850906163: 0, - 6394047531: 5 + 6394047531: 4 + } + + Names: Dict[int, str] = { + 850906163: "Ляпич", + 6394047531: "Прокопович" } # Chats diff --git a/handlers/admin.py b/handlers/admin.py index 7c98177..7ff408c 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -3,7 +3,7 @@ from aiogram.types import Message from aiogram.filters import Command from config import Config from models.state import BotState -from utils.antispam import admin_required +from utils.antispam import admin_required, saving from services.watcher_service import WatcherService from storage.message_storage import load_messages, save_message, clear_messages from logging import getLogger @@ -17,6 +17,7 @@ logger = getLogger(__name__) def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): @dp.message(Command("log")) + @saving @admin_required(3) async def send_log(message: Message): try: @@ -26,6 +27,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): await message.answer("Файл логов пока не создан.") @dp.message(Command("status")) + @saving @admin_required(3) async def send_status(message: Message): from utils.analytics import analyze_bot_logs @@ -48,6 +50,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): await message.answer(f"❌ Ошибка при проверке статуса: {str(e)}") @dp.message(Command("analytics")) + @saving @admin_required(1) async def stat(message: Message): from utils.analytics import analyze_bot_logs @@ -78,6 +81,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): save_message(sent.chat.id, sent.message_id) @dp.message(Command("power")) + @saving @admin_required(2) async def power_control(message: types.Message): args = message.text.split() diff --git a/handlers/schedule.py b/handlers/schedule.py index 92848cd..7697154 100644 --- a/handlers/schedule.py +++ b/handlers/schedule.py @@ -2,12 +2,13 @@ from aiogram import types, Dispatcher from aiogram.filters import Command from models.state import BotState from services.schedule_service import ScheduleService -from utils.antispam import is_chat_spam +from utils.antispam import is_chat_spam, saving from storage.message_storage import save_message def register_handlers(dp: Dispatcher, state: BotState): @dp.message(Command("rasp")) + @saving async def send_schedule(message: types.Message): """Отправка расписания""" if is_chat_spam(message.chat.id, state): diff --git a/services/watcher_service.py b/services/watcher_service.py index e79ff33..175bf06 100644 --- a/services/watcher_service.py +++ b/services/watcher_service.py @@ -46,6 +46,7 @@ class WatcherService: try: await self._check_all_groups() delay = randint(Config.WATCHER_BASE_DELAY, Config.WATCHER_BASE_DELAY + 100) + logger.info(f"Следущая проверка через {delay}") await asyncio.sleep(delay) except asyncio.CancelledError: break diff --git a/storage/DB.py b/storage/DB.py new file mode 100644 index 0000000..2373062 --- /dev/null +++ b/storage/DB.py @@ -0,0 +1,26 @@ +import sqlite3 +import os + +DIR = "/Users/mac/myfirstprogramm/storage/message.db" +if __name__ == "__main__": + db = sqlite3.connect(DIR) + + cursor = db.cursor() + + # cursor.execute("""CREATE TABLE message ( + # chat_id integer, + # message_id integer + # )""") + # db.commit() + + cursor.execute("SELECT * FROM message") + print(cursor.fetchone()) + + db.close() + + + +def get_db(): + return sqlite3.connect(DIR) + + diff --git a/storage/message_storage.py b/storage/message_storage.py index 2e54a2d..30910b1 100644 --- a/storage/message_storage.py +++ b/storage/message_storage.py @@ -1,23 +1,29 @@ -import os +from .DB import get_db -MESSAGES_FILE = "storage/message.txt" - -# --- функция для записи message_id --- def save_message(chat_id: int, message_id: int): - with open(MESSAGES_FILE, "a", encoding="utf-8") as f: - f.write(f"{chat_id},{message_id}\n") + db = get_db() + cur = db.cursor() + cur.execute("INSERT INTO message VALUES (?, ?)", (int(chat_id), int(message_id))) + db.commit() + cur.close() + db.close() -# --- функция для загрузки всех сообщений --- def load_messages(): - if not os.path.exists(MESSAGES_FILE): - return [] - with open(MESSAGES_FILE, "r", encoding="utf-8") as f: - lines = f.readlines() - return [tuple(map(int, line.strip().split(","))) for line in lines if line.strip()] + db = get_db() + cur = db.cursor() + cur.execute("SELECT * FROM message") + rows = cur.fetchall() + cur.close() + db.close() + return rows -# --- функция для очистки файла --- def clear_messages(): - open(MESSAGES_FILE, "w").close() + db = get_db() + cur = db.cursor() + cur.execute("DELETE FROM message") + db.commit() + cur.close() + db.close() diff --git a/utils/antispam.py b/utils/antispam.py index 305f78e..8b57edb 100644 --- a/utils/antispam.py +++ b/utils/antispam.py @@ -3,6 +3,7 @@ from functools import wraps from aiogram import types from models.state import BotState from config import Config +from storage.message_storage import save_message def is_chat_spam(chat_id: int, state: BotState) -> bool: @@ -41,3 +42,11 @@ def admin_required(need_level: int): return wrapper return decorator + +def saving(func): + """Декоратор для сохранения входящего сообщения""" + @wraps(func) + async def wrapper(message: types.Message, *args, **kwargs): + save_message(message.chat.id, message.message_id) + return await func(message, *args, **kwargs) + return wrapper \ No newline at end of file