From be9ec785f4011cc4661be7a9cd707bd6bae7db35 Mon Sep 17 00:00:00 2001 From: Niken Date: Sun, 16 Nov 2025 14:15:44 +0300 Subject: [PATCH] It's version 0.6 I add users DB --- addons/poll/handlers.py | 9 +++- handlers/schedule.py | 80 +++++++++++++++++++++++------- services/schedule_service.py | 96 +++++++++++++++++++++++++++--------- storage/DB.py | 24 +++++++-- storage/users_storage.py | 33 +++++++++++++ 5 files changed, 192 insertions(+), 50 deletions(-) create mode 100644 storage/users_storage.py diff --git a/addons/poll/handlers.py b/addons/poll/handlers.py index 82984b3..8b3a86d 100644 --- a/addons/poll/handlers.py +++ b/addons/poll/handlers.py @@ -20,7 +20,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): poll_msg = await bot.send_poll( chat_id=chat_id, question="Кто опоздает?", - options=["Я", "Я очень сильно опоздаю", "Я пиздец как опоздаю", "Наверное"], + options=["Я", "Я очень сильно опоздаю", "Я пиздец как опоздаю", "Наверное", "Не опоздаю"], is_anonymous=False, allows_multiple_answers=False, ) @@ -57,7 +57,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): save_message(msg.chat.id, msg.message_id) elif option_ids and option_ids[0] == 2: msg = await bot.send_message( - chat_id=6394047531, text=f"{username} Пиздец опоздаеты" + chat_id=6394047531, text=f"{username} Пиздец опоздает" ) save_message(msg.chat.id, msg.message_id) elif option_ids[0] == 3: @@ -65,6 +65,11 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): chat_id=6394047531, text=f"{username} возможно опоздает" ) save_message(msg.chat.id, msg.message_id) + elif option_ids[0] == 4: + msg = await bot.send_message( + chat_id=6394047531, text=f"{username} возможно опоздает" + ) + save_message(msg.chat.id, msg.message_id) elif not option_ids: msg = await bot.send_message( chat_id=6394047531, text=f"{username} Отменил свой голос" diff --git a/handlers/schedule.py b/handlers/schedule.py index c0e1bc3..6e39982 100644 --- a/handlers/schedule.py +++ b/handlers/schedule.py @@ -4,11 +4,43 @@ from models.state import BotState from services.schedule_service import ScheduleService from utils.antispam import is_chat_spam, saving from storage.message_storage import save_message +from storage.users_storage import set_group, get_group +from aiogram.utils.keyboard import InlineKeyboardBuilder +VALID_GROUPS = [ + "603Т", "33мд", "32мд", "31тс", "30тс", "29то", "28то", "27д", "26д", "25тм", "24тм", + "23д", "22мд", "21мд", "20тс", "19тс", "18то", "17то", "16д", "15д", "14тм", "13тм", + "12д", "11мд", "10мд", "8тс", "7то", "7Ст", "6то", "6Сб", "5д", "5Cа", "4Сб", "3тм", + "3Са", "2тм", "2Cб", "1Са", "600Р", "601Р", "602д" +] 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): + await message.answer("НЕ СПАМЬТЕ!!!") + return + + args = message.text.split(maxsplit=2) + # Определяем группу + if len(args) > 1: + group = args[1].strip() + else: + group = get_group(message.from_user.id) + + # Определяем смещение по дню + day_offset = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0 + + schedule_service = ScheduleService() + text, url, day, month = await schedule_service.get_schedule(group, day_offset) + + msg = await message.answer(text, parse_mode="Markdownv2") + save_message(msg.chat.id, msg.message_id) + + @dp.message(Command("prasp")) + @saving async def send_schedule(message: types.Message): """Отправка расписания""" if is_chat_spam(message.chat.id, state): @@ -16,25 +48,10 @@ def register_handlers(dp: Dispatcher, state: BotState): return args = message.text.split(maxsplit=2) - group = args[1].strip() if len(args) > 1 else "30тс" - day_offset = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0 - - schedule_service = ScheduleService() - text, url, day, month = await schedule_service.get_schedule(group, day_offset) - # Отправляем текст расписания - msg = await message.answer(text, parse_mode="Markdownv2") - - save_message(message.chat.id, msg.message_id) - - @dp.message(Command("prasp")) - async def send_schedule(message: types.Message): - """Отправка расписания""" - if is_chat_spam(message.chat.id, state): - await message.answer("НЕ СПАМЬТЕ!!!") - return - - args = message.text.split(maxsplit=2) - group = args[1].strip() if len(args) > 1 else "30тс" + if len(args) > 1: + group = args[1].strip() + else: + group = get_group(message.from_user.id) day_offset = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0 schedule_service = ScheduleService() @@ -54,3 +71,28 @@ def register_handlers(dp: Dispatcher, state: BotState): state.file_id_cache[group.lower()] = msg.photo[-1].file_id else: await message.answer(f"Не удалось найти расписание для {group}") + + @dp.message(Command("set")) + @saving + async def set(message: types.Message): + # создаём клавиатуру + builder = InlineKeyboardBuilder() + for group in VALID_GROUPS: + builder.button(text=group, callback_data=f"set_group:{group}") + builder.adjust(5) # количество кнопок в строке + + await message.answer( + "Выбери группу из списка:", + reply_markup=builder.as_markup() + ) + + @dp.callback_query(lambda c: c.data.startswith("set_group:")) + async def process_group_choice(callback: types.CallbackQuery): + group = callback.data.split(":")[1] + set_group(callback.from_user.id, group) + + # редактируем сообщение: убираем клавиатуру + await callback.message.edit_reply_markup(reply_markup=None) + + await callback.message.answer(f"✅ Группа установлена: {group}") + await callback.answer() diff --git a/services/schedule_service.py b/services/schedule_service.py index f39cc92..847880c 100644 --- a/services/schedule_service.py +++ b/services/schedule_service.py @@ -1,14 +1,15 @@ from datetime import datetime, timedelta from typing import Optional, Tuple -from playwright.async_api import async_playwright, ViewportSize, FloatRect +from playwright.async_api import async_playwright import logging import aiohttp from bs4 import BeautifulSoup import ssl import certifi +import re logger = logging.getLogger(__name__) - +BOUNDARY = r"[^0-9A-Za-zА-Яа-яЁё]" class ScheduleService: def __init__(self): @@ -32,8 +33,10 @@ class ScheduleService: int(d.month), ) + import re + async def get_schedule( - self, group: str, day_offset: int = 0 + self, group: str, day_offset: int = 0 ) -> Tuple[str, str, int, int]: """Получение текста расписания (аналог Rust parse_schedule)""" url, day, month = self._make_url(day_offset) @@ -48,9 +51,8 @@ class ScheduleService: "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" } - # тут можно использовать aiohttp + chardet/charset_normalizer async with aiohttp.ClientSession( - connector=connector, headers=headers + connector=connector, headers=headers ) as session: async with session.get(url) as resp: raw_bytes = await resp.read() @@ -58,15 +60,18 @@ class ScheduleService: decoded = raw_bytes.decode("cp1251", errors="ignore") document = BeautifulSoup(decoded, "html.parser") - # ищем

... elements = document.select("p.MsoPlainText b") found_group = False schedule_lines = [] + + # регулярка: ищем точное совпадение группы как отдельного слова + group_pattern = re.compile(rf"\b{re.escape(group)}\b", re.IGNORECASE) + for el in elements: text = el.get_text(strip=True) if not found_group: - if group in text: + if group_pattern.search(text): found_group = True schedule_lines.append(text) else: @@ -86,39 +91,81 @@ class ScheduleService: return result, url, day, month + + + def exact_group_regex(self, group: str) -> re.Pattern: + # ищем как отдельный токен: граница слева/справа или начало/конец + pattern = rf"(^|{BOUNDARY}){re.escape(group)}({BOUNDARY}|$)" + return re.compile(pattern) + async def get_pschedule( - self, group: str, day_offset: int = 0 + self, group: str, day_offset: int = 0 ) -> Tuple[Optional[bytes], str, int, int]: - """Получение скриншота расписания""" url, day, month = self._make_url(day_offset) async with async_playwright() as p: browser = await p.chromium.launch(headless=True) - context = await browser.new_context( - viewport=ViewportSize(width=400, height=3000) - ) + context = await browser.new_context(viewport={"width": 400, "height": 3000}) page = await context.new_page() try: response = await page.goto(url, wait_until="networkidle", timeout=30000) - if not response or response.status != 200: logger.warning(f"Ошибка загрузки страницы: {url}") return None, url, day, month - locator = page.locator("p", has_text=group).first - if await locator.count() > 0: - await locator.scroll_into_view_if_needed() - box = await locator.bounding_box() + # 1) сначала пытаемся по более точному селектору (как в HTML-парсере) + candidates = page.locator("p.MsoPlainText b") + count = await candidates.count() + regex = self.exact_group_regex(group) + target_handle = None + + for i in range(count): + el = candidates.nth(i) + text = (await el.inner_text()).strip() + if regex.search(text): + # нашли b с нужной группой — возьмём родительский p для удобного скрина + parent_p = await el.locator("xpath=ancestor::p[1]").element_handle() + target_handle = parent_p or await el.element_handle() + break + + # 2) если не нашли в p.MsoPlainText b, попробуем просто p b или p + if not target_handle: + candidates = page.locator("p b") + count = await candidates.count() + for i in range(count): + el = candidates.nth(i) + text = (await el.inner_text()).strip() + if regex.search(text): + parent_p = await el.locator("xpath=ancestor::p[1]").element_handle() + target_handle = parent_p or await el.element_handle() + break + + if not target_handle: + # последний шанс: любые

+ candidates = page.locator("p") + count = await candidates.count() + for i in range(count): + el = candidates.nth(i) + text = (await el.inner_text()).strip() + if regex.search(text): + target_handle = await el.element_handle() + break + + if target_handle: + # скроллим и получаем box + await target_handle.scroll_into_view_if_needed() + box = await target_handle.bounding_box() if box: - clip_rect = FloatRect( - x=float(max(box["x"] - 0, 0)), - y=float(max(box["y"] - 0, 0)), - width=float(box["width"] + 150), - height=float(box["height"] + 100), - ) - return await page.screenshot(clip=clip_rect), url, day, month + clip_rect = { + "x": float(max(box["x"], 0)), + "y": float(max(box["y"], 0)), + "width": float(box["width"] + 150), + "height": float(box["height"] + 100), + } + img = await page.screenshot(clip=clip_rect) + return img, url, day, month except Exception as e: logger.error(f"Ошибка при получении расписания: {e}") @@ -127,3 +174,4 @@ class ScheduleService: await browser.close() return None, url, day, month + diff --git a/storage/DB.py b/storage/DB.py index a3f1aa2..d98b7d1 100644 --- a/storage/DB.py +++ b/storage/DB.py @@ -1,19 +1,33 @@ import sqlite3 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 + # создаём таблицы (лучше добавить IF NOT EXISTS) + cursor.execute("""CREATE TABLE IF NOT EXISTS message ( + chat_id INTEGER, + message_id INTEGER )""") + + cursor.execute("""CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER, + user_group TEXT + )""") + + # добавим тестовые данные + cursor.execute("INSERT INTO message VALUES (?, ?)", (1, 100)) + cursor.execute("INSERT INTO users VALUES (?, ?)", (42, 'admin')) db.commit() + # читаем данные cursor.execute("SELECT * FROM message") - print(cursor.fetchone()) + print("Message:", cursor.fetchall()) + + cursor.execute("SELECT * FROM users") + print("Users:", cursor.fetchall()) db.close() diff --git a/storage/users_storage.py b/storage/users_storage.py new file mode 100644 index 0000000..037cb3c --- /dev/null +++ b/storage/users_storage.py @@ -0,0 +1,33 @@ +from .DB import get_db + +def save_user(user_id: int, group: str = "30тс"): + db = get_db() + cur = db.cursor() + cur.execute("INSERT INTO users (user_id, user_group) VALUES (?, ?)", (user_id, group)) + db.commit() + cur.close() + db.close() + +def set_group(user_id: int, group: str = "30тс"): + db = get_db() + cur = db.cursor() + cur.execute("UPDATE users SET user_group = ? WHERE user_id = ?", (group, user_id)) + db.commit() + cur.close() + db.close() + +def get_group(user_id: int, default: str = "30тс") -> str: + db = get_db() + cur = db.cursor() + cur.execute("SELECT user_group FROM users WHERE user_id = ?", (user_id,)) + row = cur.fetchone() + if row: + group = row[0] + else: + # если пользователя нет — регистрируем с дефолтной группой + cur.execute("INSERT INTO users (user_id, user_group) VALUES (?, ?)", (user_id, default)) + db.commit() + group = default + cur.close() + db.close() + return group