It's version 0.6 I add users DB
This commit is contained in:
@@ -20,7 +20,7 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
|||||||
poll_msg = await bot.send_poll(
|
poll_msg = await bot.send_poll(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
question="Кто опоздает?",
|
question="Кто опоздает?",
|
||||||
options=["Я", "Я очень сильно опоздаю", "Я пиздец как опоздаю", "Наверное"],
|
options=["Я", "Я очень сильно опоздаю", "Я пиздец как опоздаю", "Наверное", "Не опоздаю"],
|
||||||
is_anonymous=False,
|
is_anonymous=False,
|
||||||
allows_multiple_answers=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)
|
save_message(msg.chat.id, msg.message_id)
|
||||||
elif option_ids and option_ids[0] == 2:
|
elif option_ids and option_ids[0] == 2:
|
||||||
msg = await bot.send_message(
|
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)
|
save_message(msg.chat.id, msg.message_id)
|
||||||
elif option_ids[0] == 3:
|
elif option_ids[0] == 3:
|
||||||
@@ -65,6 +65,11 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
|||||||
chat_id=6394047531, text=f"{username} возможно опоздает"
|
chat_id=6394047531, text=f"{username} возможно опоздает"
|
||||||
)
|
)
|
||||||
save_message(msg.chat.id, msg.message_id)
|
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:
|
elif not option_ids:
|
||||||
msg = await bot.send_message(
|
msg = await bot.send_message(
|
||||||
chat_id=6394047531, text=f"{username} Отменил свой голос"
|
chat_id=6394047531, text=f"{username} Отменил свой голос"
|
||||||
|
|||||||
+61
-19
@@ -4,11 +4,43 @@ from models.state import BotState
|
|||||||
from services.schedule_service import ScheduleService
|
from services.schedule_service import ScheduleService
|
||||||
from utils.antispam import is_chat_spam, saving
|
from utils.antispam import is_chat_spam, saving
|
||||||
from storage.message_storage import save_message
|
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):
|
def register_handlers(dp: Dispatcher, state: BotState):
|
||||||
@dp.message(Command("rasp"))
|
@dp.message(Command("rasp"))
|
||||||
@saving
|
@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):
|
async def send_schedule(message: types.Message):
|
||||||
"""Отправка расписания"""
|
"""Отправка расписания"""
|
||||||
if is_chat_spam(message.chat.id, state):
|
if is_chat_spam(message.chat.id, state):
|
||||||
@@ -16,25 +48,10 @@ def register_handlers(dp: Dispatcher, state: BotState):
|
|||||||
return
|
return
|
||||||
|
|
||||||
args = message.text.split(maxsplit=2)
|
args = message.text.split(maxsplit=2)
|
||||||
group = args[1].strip() if len(args) > 1 else "30тс"
|
if len(args) > 1:
|
||||||
day_offset = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0
|
group = args[1].strip()
|
||||||
|
else:
|
||||||
schedule_service = ScheduleService()
|
group = get_group(message.from_user.id)
|
||||||
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тс"
|
|
||||||
day_offset = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0
|
day_offset = int(args[2]) if len(args) > 2 and args[2].isdigit() else 0
|
||||||
|
|
||||||
schedule_service = ScheduleService()
|
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
|
state.file_id_cache[group.lower()] = msg.photo[-1].file_id
|
||||||
else:
|
else:
|
||||||
await message.answer(f"Не удалось найти расписание для {group}")
|
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()
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
from playwright.async_api import async_playwright, ViewportSize, FloatRect
|
from playwright.async_api import async_playwright
|
||||||
import logging
|
import logging
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import ssl
|
import ssl
|
||||||
import certifi
|
import certifi
|
||||||
|
import re
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
BOUNDARY = r"[^0-9A-Za-zА-Яа-яЁё]"
|
||||||
|
|
||||||
class ScheduleService:
|
class ScheduleService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -32,6 +33,8 @@ class ScheduleService:
|
|||||||
int(d.month),
|
int(d.month),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
async def get_schedule(
|
async def get_schedule(
|
||||||
self, group: str, day_offset: int = 0
|
self, group: str, day_offset: int = 0
|
||||||
) -> Tuple[str, str, int, int]:
|
) -> Tuple[str, str, int, int]:
|
||||||
@@ -48,7 +51,6 @@ 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"
|
"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(
|
async with aiohttp.ClientSession(
|
||||||
connector=connector, headers=headers
|
connector=connector, headers=headers
|
||||||
) as session:
|
) as session:
|
||||||
@@ -58,15 +60,18 @@ class ScheduleService:
|
|||||||
decoded = raw_bytes.decode("cp1251", errors="ignore")
|
decoded = raw_bytes.decode("cp1251", errors="ignore")
|
||||||
document = BeautifulSoup(decoded, "html.parser")
|
document = BeautifulSoup(decoded, "html.parser")
|
||||||
|
|
||||||
# ищем <p class="MsoPlainText"><b>...</b>
|
|
||||||
elements = document.select("p.MsoPlainText b")
|
elements = document.select("p.MsoPlainText b")
|
||||||
|
|
||||||
found_group = False
|
found_group = False
|
||||||
schedule_lines = []
|
schedule_lines = []
|
||||||
|
|
||||||
|
# регулярка: ищем точное совпадение группы как отдельного слова
|
||||||
|
group_pattern = re.compile(rf"\b{re.escape(group)}\b", re.IGNORECASE)
|
||||||
|
|
||||||
for el in elements:
|
for el in elements:
|
||||||
text = el.get_text(strip=True)
|
text = el.get_text(strip=True)
|
||||||
if not found_group:
|
if not found_group:
|
||||||
if group in text:
|
if group_pattern.search(text):
|
||||||
found_group = True
|
found_group = True
|
||||||
schedule_lines.append(text)
|
schedule_lines.append(text)
|
||||||
else:
|
else:
|
||||||
@@ -86,39 +91,81 @@ class ScheduleService:
|
|||||||
|
|
||||||
return result, url, day, month
|
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(
|
async def get_pschedule(
|
||||||
self, group: str, day_offset: int = 0
|
self, group: str, day_offset: int = 0
|
||||||
) -> Tuple[Optional[bytes], str, int, int]:
|
) -> Tuple[Optional[bytes], str, int, int]:
|
||||||
"""Получение скриншота расписания"""
|
|
||||||
url, day, month = self._make_url(day_offset)
|
url, day, month = self._make_url(day_offset)
|
||||||
|
|
||||||
async with async_playwright() as p:
|
async with async_playwright() as p:
|
||||||
browser = await p.chromium.launch(headless=True)
|
browser = await p.chromium.launch(headless=True)
|
||||||
context = await browser.new_context(
|
context = await browser.new_context(viewport={"width": 400, "height": 3000})
|
||||||
viewport=ViewportSize(width=400, height=3000)
|
|
||||||
)
|
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await page.goto(url, wait_until="networkidle", timeout=30000)
|
response = await page.goto(url, wait_until="networkidle", timeout=30000)
|
||||||
|
|
||||||
if not response or response.status != 200:
|
if not response or response.status != 200:
|
||||||
logger.warning(f"Ошибка загрузки страницы: {url}")
|
logger.warning(f"Ошибка загрузки страницы: {url}")
|
||||||
return None, url, day, month
|
return None, url, day, month
|
||||||
|
|
||||||
locator = page.locator("p", has_text=group).first
|
# 1) сначала пытаемся по более точному селектору (как в HTML-парсере)
|
||||||
if await locator.count() > 0:
|
candidates = page.locator("p.MsoPlainText b")
|
||||||
await locator.scroll_into_view_if_needed()
|
count = await candidates.count()
|
||||||
box = await locator.bounding_box()
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
# последний шанс: любые <p>
|
||||||
|
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:
|
if box:
|
||||||
clip_rect = FloatRect(
|
clip_rect = {
|
||||||
x=float(max(box["x"] - 0, 0)),
|
"x": float(max(box["x"], 0)),
|
||||||
y=float(max(box["y"] - 0, 0)),
|
"y": float(max(box["y"], 0)),
|
||||||
width=float(box["width"] + 150),
|
"width": float(box["width"] + 150),
|
||||||
height=float(box["height"] + 100),
|
"height": float(box["height"] + 100),
|
||||||
)
|
}
|
||||||
return await page.screenshot(clip=clip_rect), url, day, month
|
img = await page.screenshot(clip=clip_rect)
|
||||||
|
return img, url, day, month
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при получении расписания: {e}")
|
logger.error(f"Ошибка при получении расписания: {e}")
|
||||||
@@ -127,3 +174,4 @@ class ScheduleService:
|
|||||||
await browser.close()
|
await browser.close()
|
||||||
|
|
||||||
return None, url, day, month
|
return None, url, day, month
|
||||||
|
|
||||||
|
|||||||
+19
-5
@@ -1,19 +1,33 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
DIR = "/Users/mac/myfirstprogramm/storage/message.db"
|
DIR = "/Users/mac/myfirstprogramm/storage/message.db"
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
db = sqlite3.connect(DIR)
|
db = sqlite3.connect(DIR)
|
||||||
|
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
|
|
||||||
cursor.execute("""CREATE TABLE message (
|
# создаём таблицы (лучше добавить IF NOT EXISTS)
|
||||||
chat_id integer,
|
cursor.execute("""CREATE TABLE IF NOT EXISTS message (
|
||||||
message_id integer
|
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()
|
db.commit()
|
||||||
|
|
||||||
|
# читаем данные
|
||||||
cursor.execute("SELECT * FROM message")
|
cursor.execute("SELECT * FROM message")
|
||||||
print(cursor.fetchone())
|
print("Message:", cursor.fetchall())
|
||||||
|
|
||||||
|
cursor.execute("SELECT * FROM users")
|
||||||
|
print("Users:", cursor.fetchall())
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user