Compare commits

..

3 Commits

Author SHA1 Message Date
7495bd949b hotfix: log any update and use index for excels
All checks were successful
Build and Run VSTU Public TG bot / build_and_run (push) Successful in 27s
2026-04-13 23:10:54 +03:00
8bbb440bce fix: user_id -> chat_id for unsub:
All checks were successful
Build and Run VSTU Public TG bot / build_and_run (push) Successful in 6s
2026-04-05 22:43:04 +03:00
64c3f27386 fix: missing unwrap with .scalar_one_or_none
All checks were successful
Build and Run VSTU Public TG bot / build_and_run (push) Successful in 8s
2026-04-05 22:40:30 +03:00
7 changed files with 96 additions and 24 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.env
__pycache__

Binary file not shown.

View File

@@ -4,11 +4,13 @@ import logging
logger = logging.getLogger(__name__)
class DataManager:
def __init__(self, facultets_url, groups_url):
def __init__(self, facultets_url, groups_url, parser_url):
self.facultets_url = facultets_url
self.groups_url = groups_url
self.parser_url = parser_url
self.facs = {} # {id: {name: ...}}
self.groups = {} # {id: {real_name: ..., facultet_tech: ..., ...}}
self.parser = {}
async def refresh_cache(self, connector):
async with aiohttp.ClientSession(connector=connector) as session:
@@ -17,6 +19,8 @@ class DataManager:
try:
self.facs = await fetch(self.facultets_url)
self.groups = (await fetch(self.groups_url))['groups']
self.parser = (await fetch(self.parser_url))
logger.info(f"Loaded {len(self.facs)} facs and {len(self.groups)} groups")
except Exception as e:
logger.error(f"Cache refresh failed: {e}")
@@ -29,11 +33,12 @@ class DataManager:
def get_files_by_fac(self, fac_id):
files = {} # {uniqpath*: display_name}
for g in self.groups.values():
if g.get('facultet_tech') == fac_id or g.get('facultet_recognized') == fac_id:
for ex in g.get('excels', []):
fn = ex['uniqpath'].replace("vstu.ru/rasp?dep=", "")
files[fn] = ex['display_filename']
i = -1
for ex in self.parser['all_files']:
i += 1
if ex.get('facultet') == fac_id:
files[i] = ex['display_filename']
return list(files.items())
def get_excel_by_uniqpath(self, uniqid):

49
main.py
View File

@@ -22,12 +22,22 @@ import keyboards as kb
import re
import fnmatch
from middleware import LoggingMiddleware
load_dotenv()
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler()
]
)
# --- Настройки ---
engine = create_async_engine(os.getenv("DATABASE_URL"))
async_session = async_sessionmaker(engine, expire_on_commit=False)
data_mgr = DataManager(os.getenv("FACULTETS_JSON_URL"), os.getenv("GROUPS_JSON_URL"))
data_mgr = DataManager(os.getenv("FACULTETS_JSON_URL"), os.getenv("GROUPS_JSON_URL"), os.getenv("PARSER_JSON_URL"))
bot = None
dp = Dispatcher()
@@ -203,16 +213,17 @@ async def view_group(callback: types.CallbackQuery):
# Просмотр файла
@dp.callback_query(F.data.startswith("view_excel:"))
async def view_excel(callback: types.CallbackQuery):
pre_uniq_path = callback.data.split(":")[1]
uniqpath = "vstu.ru/rasp?dep=" + pre_uniq_path
pre_index = callback.data.split(":")[1]
# Находим имя файла для красоты
h_name = "Excel файл"
for g in data_mgr.groups.values():
for ex in g.get('excels', []):
if ex.get('uniqpath', "") == uniqpath: h_name = ex['display_filename']; break
try:
ex = data_mgr.parser['all_files'][int(pre_index)]
h_name = ex['display_filename']
except Exception as e:
print(f"safe?: {e}")
builder = InlineKeyboardBuilder()
builder.row(types.InlineKeyboardButton(text="🔔 Подписаться на файл", callback_data=f"suco:excel:{pre_uniq_path}"))
builder.row(types.InlineKeyboardButton(text="🔔 Подписаться на файл", callback_data=f"suco:excel:{pre_index}"))
builder.row(types.InlineKeyboardButton(text="⬅️ Назад", callback_data="menu_start"))
await callback.message.edit_text(f"📄 Файл: *{h_name}*", reply_markup=builder.as_markup(), parse_mode="Markdown")
@@ -224,11 +235,13 @@ async def sub_confirm(callback: types.CallbackQuery):
sub_value = val
if mode == "excel":
pre_uniq_path = h_name
uniqpath = "vstu.ru/rasp?dep=" + pre_uniq_path
excel = data_mgr.get_excel_by_uniqpath(uniqpath)
sub_value = excel['url'].split("/")[-1]
h_name = sub_value
pre_index = h_name
try:
excel = data_mgr.parser['all_files'][int(pre_index)]
sub_value = excel['url'].split("/")[-1]
h_name = sub_value
except Exception as e:
print(f"safe? e={e}")
async with async_session() as session:
# Регистрация пользователя если нет
@@ -270,18 +283,18 @@ async def my_subs(callback: types.CallbackQuery):
@dp.callback_query(F.data.startswith("unsub:"))
async def unsub(callback: types.CallbackQuery):
sid = int(callback.data.split(":")[1])
user_id = callback.from_user.id # ID того, кто нажал на кнопку
chat_id = callback.message.chat.id
async with async_session() as session:
# Добавляем условие chat_id == user_id
stmt = select(Subscription).where(
Subscription.id == sid,
Subscription.chat_id == user_id,
Subscription.chat_id == chat_id,
Subscription.deleted == False
)
result = await session.execute(stmt)
result.deleted = True
session.add(result)
sub = result.scalar_one_or_none()
sub.deleted = True
session.add(sub)
await session.commit()
# Проверяем, было ли реально что-то удалено
@@ -337,6 +350,8 @@ async def main():
await conn.run_sync(Base.metadata.create_all)
await data_mgr.refresh_cache(connector)
dp.update.middleware(LoggingMiddleware()) # <-- добавьте эту строку
asyncio.create_task(start_rabbitmq_consumer())
# Периодическое обновление индексов

51
middleware.py Normal file
View File

@@ -0,0 +1,51 @@
from aiogram.types import Update
from aiogram import BaseMiddleware
from typing import Callable, Dict, Any, Awaitable
import logging
import time
import json
class LoggingMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Update,
data: Dict[str, Any]
) -> Any:
# Логируем входящий апдейт
update_id = event.update_id
update_type = "unknown"
if event.message:
update_type = "message"
chat_id = event.message.chat.id
text = event.message.text or "[non-text]"
log_msg = f"📥 [UPDATE {update_id}] type={update_type} chat={chat_id} text={text}"
elif event.callback_query:
update_type = "callback_query"
chat_id = event.callback_query.message.chat.id
data_str = event.callback_query.data or "[no data]"
log_msg = f"📥 [UPDATE {update_id}] type={update_type} chat={chat_id} data={data_str}"
elif event.my_chat_member:
update_type = "my_chat_member"
log_msg = f"📥 [UPDATE {update_id}] type={update_type}"
elif event.chat_member:
update_type = "chat_member"
log_msg = f"📥 [UPDATE {update_id}] type={update_type}"
else:
log_msg = f"📥 [UPDATE {update_id}] type={update_type} raw={event.json()}"
# Печатаем или используем logging
logging.info(log_msg)
# Если настроен logging: logging.info(log_msg)
# Передаём управление дальше (в хендлеры)
try:
result = await handler(event, data)
except Exception as e:
# Логируем ошибку, но не подавляем
logging.error(f"❌ [UPDATE {update_id}] handler error: {e}")
raise
else:
logging.info(f"✅ [UPDATE {update_id}] handled successfully")
return result