Skip to content
Open
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ ignore =
E712
; Line too long (133 > 120 characters)
E501
;the function is too complex
C901

per-file-ignores =
; all tests
Expand Down
67 changes: 67 additions & 0 deletions bot/database/task_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,70 @@ def missed_user_tasks(self, user: User):
)
.all()
)

def decided_users(self, task_name: str):
completed_tasks = (
self.session.query(Task)
.filter(Task.name == task_name, Task.completed == True)
.all()
)
user_ids = {task.assigned_user_id for task in completed_tasks}

return len(user_ids)

def all_users(self, task_name: str):
tasks = (
self.session.query(Task)
.filter(
Task.name == task_name,
)
.all()
)
user_ids = {task.assigned_user_id for task in tasks}

return len(user_ids)

def index_of_time(self, task_name: str, assigned_user_id: int):
task = (
self.session.query(Task)
.filter(
Task.name == task_name,
Task.assigned_user_id == assigned_user_id,
Task.completed == True,
)
.first()
)

if task is None or task.deadline is None:
return 0 # Задача не найдена или у задачи нет дедлайна

time_taken = datetime.now() - task.deadline
time_until_deadline = (
task.deadline - datetime.now()
) # отрицательное, если просрочено

# Абсолютные значения времени в секундах
time_taken_sec = abs(time_taken.total_seconds())
time_until_deadline_sec = abs(time_until_deadline.total_seconds())

if time_taken_sec == 0:
return None # избегаем деления на ноль

ratio = round(time_until_deadline_sec / time_taken_sec, 2)

return ratio

def score_for_tasks(self, task_name: str, user_id: int):
from settings import Config

S_min = Config.s_min
N = self.decided_users(task_name)
N_total = self.all_users(task_name)
time_index = self.index_of_time(task_name, user_id)

if time_index is None:
return S_min # если задача не выполнена или нет дедлайна

R_time = min(time_index, 0.35)
score = max(S_min, 500 * (1 - N / N_total) * (1 + R_time))
return score
27 changes: 5 additions & 22 deletions bot/database/user_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ def create_user(
def get_all_students(self) -> List[User]:
"""Получить всех студентов (исключая учителей по tg_id)."""
return (
self.session.query(User)
.filter(User.tg_id.notin_(config.teacher_ids))
.all()
self.session.query(User).filter(User.tg_id.notin_(config.teacher_ids)).all()
)

def get_all_active_students(self) -> List[User]:
Expand All @@ -47,20 +45,12 @@ def get_all_active_students(self) -> List[User]:

def get_user_id_by_username(self, username: str) -> Optional[int]:
"""Вернуть внутренний ID пользователя по username или None."""
user = (
self.session.query(User)
.filter(User.username == username)
.first()
)
user = self.session.query(User).filter(User.username == username).first()
return user.id if user else None

def get_user_by_tg_id(self, tg_id: int) -> Optional[User]:
"""Получить пользователя по его Telegram ID."""
return (
self.session.query(User)
.filter(User.tg_id == tg_id)
.first()
)
return self.session.query(User).filter(User.tg_id == tg_id).first()

def get_all_students_with_tasks(self) -> List[User]:
"""Получить всех студентов вместе с их невыполненными заданиями."""
Expand All @@ -85,18 +75,11 @@ def heal(self, user: User) -> None:
def get_teachers(self) -> List[User]:
"""Получить всех учителей (старшекурсников) по tg_id."""
teachers = (
self.session.query(User)
.filter(User.tg_id.in_(config.teacher_ids))
.all()
self.session.query(User).filter(User.tg_id.in_(config.teacher_ids)).all()
)
logger.info(f"Получены учителя: {teachers}")
return teachers

def leaderboard(self, limit: int = 20) -> List[User]:
"""Извлечь топ-`limit` пользователей, сортируя по убыванию points."""
return (
self.session.query(User)
.order_by(User.points.desc())
.limit(limit)
.all()
)
return self.session.query(User).order_by(User.points.desc()).limit(limit).all()
8 changes: 4 additions & 4 deletions bot/handlers/leaderboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ def format_user_status(user: User, top_rating: list[User]) -> list[str]:
messages.append(f"🎉 Вы на {rank}-м месте в топ-20!")
else:
if user.points == 0:
messages.append("😔 У вас пока нет баллов. Решайте задачи, чтобы попасть в топ!")
messages.append(
"😔 У вас пока нет баллов. Решайте задачи, чтобы попасть в топ!"
)
else:
last_top_score = top_rating[-1].points if top_rating else 0
needed = last_top_score - user.points + 1
messages.append(
f"👉 Чтобы войти в топ-20, нужно ещё {needed} балл(ов)."
)
messages.append(f"👉 Чтобы войти в топ-20, нужно ещё {needed} балл(ов).")
return messages


Expand Down
1 change: 1 addition & 0 deletions bot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,6 @@ async def main():

await dp.start_polling(bot)


if __name__ == "__main__":
asyncio.run(main())
4 changes: 3 additions & 1 deletion bot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class Config(BaseSettings):
BOT_TOKEN: str = (
"8163900085:AAFw6f80JCzYlc77bvxeP9hhbMaEeveE8Is" # your tg bot token from botfather
"7664854738:AAGMUTXm2uT7eUR4O8tl7lLs145f7Fv5KoM" # your tg bot token from botfather
)
DATABASE_URL: str = "postgresql://ctf:ctf@localhost:5432/ctf"
ADMIN_NICKNAMES: str = "tgadminnick1,tgadminnick2"
Expand All @@ -19,6 +19,8 @@ class Config(BaseSettings):
ENV: str = "dev"
minimum_xp_count_to_heal: int = 10
_teacher_ids: list[int] = "393200400,704339275"

s_min: int = 50
HEAL_LIMIT: int = 3

@property
Expand Down
96 changes: 65 additions & 31 deletions bot/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from utils.notifications import Notifications
from database.db import get_db
from database.user_dao import UserDAO
from database.task_dao import TaskDao

from utils.root_me import get_solved_tasks_of_student

Expand All @@ -15,40 +16,73 @@
async def sync_education_tasks(bot: Bot):
while True:
with get_db() as session:
# Fetch all users with their tasks
dao = UserDAO(session)
users = dao.get_all_students_with_tasks()
for user in users:
if user.root_me_nickname:
# try:
solved_tasks = await get_solved_tasks_of_student(
user.root_me_nickname
)
for task in user.tasks:
task.completed = task.name in solved_tasks
if not task.completed and task.is_expired and not task.violation_recorded:
user.lives -= 1
user.violations += 1
task.violation_recorded = True # Отмечаем, что нарушение обработано
teacher_message = (
f"Задача {task.name} истека у студента {user}."
)
logger.info(teacher_message)
notify = Notifications(bot)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ты наверное случайно удалил строчку? не упадет ли программа?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

да случайно

await notify.say_about_deadline_fail(teacher_message)
student_message = f"Ты потерял 1 HP за задачу {task.name}. 😢 Пожалуйста, старайся выполнять задания вовремя, чтобы избежать потерь.\
Если у тебя есть вопросы или трудности, не стесняйся обращаться за помощью в общий чат."
logger.info(student_message)
await notify._say_student(user, student_message)

session.commit()
logger.info(f"Synced tasks for user: {user.username}")
# except Exception as e:
# logger.error(f"Error syncing tasks for {user.username}: {e.}")
await asyncio.sleep(60)
await process_all_users_tasks(bot, session)
await asyncio.sleep(0.1)


async def process_all_users_tasks(bot: Bot, session):
dao = UserDAO(session)
users = dao.get_all_students_with_tasks()
for user in users:
if user.root_me_nickname:
await process_user_tasks(bot, session, user)
logger.info(f"Synced tasks for user: {user.username}")
await asyncio.sleep(60)


async def process_user_tasks(bot: Bot, session, user):
task_dao = TaskDao(session)
notify = Notifications(bot)

try:
solved_tasks = await get_solved_tasks_of_student(user.root_me_nickname)
for task in user.tasks:
await process_single_task(task_dao, notify, user, task, solved_tasks)
session.commit()
except Exception as e:
logger.error(f"Error syncing tasks for {user.username}: {e}")


async def process_single_task(task_dao, notify, user, task, solved_tasks):
task.completed = task.name in solved_tasks

if task.completed:
await handle_completed_task(task_dao, notify, user, task)
elif not task.completed and task.is_expired and not task.violation_recorded:
await handle_expired_task(notify, user, task)


async def handle_completed_task(task_dao, notify, user, task):
score = task_dao.score_for_tasks(task.name, user.id)
user.points += score

student_message = f"Молодец, ты решил задачу {task.name} и получил {score} очков"
admin_log = f"{user.username} - {user.full_name} решил задачу {task.name} и получил {score} очков"

logger.info(admin_log)
await notify._say_teachers(admin_log)
await notify._say_student(user, student_message)


async def handle_expired_task(notify, user, task):
user.lives -= 1
user.violations += 1
task.violation_recorded = True

teacher_message = f"Задача {task.name} истека у студента {user}."
logger.info(teacher_message)

await notify.say_about_deadline_fail(teacher_message)

student_message = (
f"Ты потерял 1 HP за задачу {task.name}. 😢 Пожалуйста, старайся выполнять задания вовремя, "
"чтобы избежать потерь. Если у тебя есть вопросы или трудности, "
"не стесняйся обращаться за помощью в общий чат."
)
logger.info(student_message)
await notify._say_student(user, student_message)


async def restore_student_lives():
"""Восстановление жизней всех активных студентов до 3-х."""
try:
Expand Down
2 changes: 1 addition & 1 deletion bot/utils/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async def _say_teachers(self, message: str):
async def _say_student(self, student: User, message: str):
"""Написать студенту о чем-то."""
try:
await self.bot.send_message(chat_id=student.tg_id, text=message)
await self.bot.send_message(text=message, chat_id=student.tg_id)
logger.info(f"Sent {message} to {student.full_name} - @{student.username}")
except TelegramBadRequest:
logger.warning(
Expand Down