Compare commits
5 Commits
30bca7a64a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
7495bd949b
|
|||
|
8bbb440bce
|
|||
|
64c3f27386
|
|||
|
8d84931fca
|
|||
|
7f890e927a
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.env
|
||||
__pycache__
|
||||
6
Dockerfile
Normal file
6
Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
CMD ["python", "-u", "main.py"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
49
main.py
@@ -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
51
middleware.py
Normal 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
|
||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
aio_pika==9.6.2
|
||||
aiogram==3.27.0
|
||||
aiohttp==3.11.16
|
||||
aiohttp_socks==0.11.0
|
||||
python-dotenv==1.2.2
|
||||
SQLAlchemy==2.0.49
|
||||
asyncpg
|
||||
Reference in New Issue
Block a user