import asyncio
import logging
from datetime import datetime
from typing import Optional
import aiosqlite
from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import (
CallbackQuery,
InlineKeyboardButton,
InlineKeyboardMarkup,
KeyboardButton,
Message,
ReplyKeyboardMarkup,
)
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from keyboard import welcom
BOT_TOKEN = "8239386496:AAGztigeslO6Zn27mWAeoFP03VDSgvaNhzI"
DB_NAME = "diary.db"
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
router = Router()
# ==================== STATES ====================
class HWState(StatesGroup):
subject = State()
desc = State()
deadline = State()
class GradeState(StatesGroup):
subject = State()
value = State()
class ScheduleState(StatesGroup):
day = State()
text = State()
class RemindState(StatesGroup):
text = State()
time = State()
class NotifyTimeState(StatesGroup):
time = State()
# ==================== DATABASE ====================
async def init_db():
async with aiosqlite.connect(DB_NAME) as db:
await db.executescript("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
xp INTEGER DEFAULT 0,
notifications INTEGER DEFAULT 1,
notify_time TEXT DEFAULT '09:00'
);
CREATE TABLE IF NOT EXISTS schedule (
user_id INTEGER, day TEXT, tasks TEXT,
PRIMARY KEY(user_id, day)
);
CREATE TABLE IF NOT EXISTS homework (
id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER,
subject TEXT, desc TEXT, deadline DATETIME,
is_done INTEGER DEFAULT 0, notify_level INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS grades (
user_id INTEGER, subject TEXT, grade INTEGER
);
CREATE TABLE IF NOT EXISTS reminders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER, text TEXT, remind_at DATETIME
);
""")
# Migration: add missing columns to existing users table
try:
await db.execute("ALTER TABLE users ADD COLUMN notifications INTEGER DEFAULT 1")
except aiosqlite.OperationalError:
pass # Column already exists
try:
await db.execute("ALTER TABLE users ADD COLUMN notify_time TEXT DEFAULT '09:00'")
except aiosqlite.OperationalError:
pass # Column already exists
await db.commit()
async def db_get_user(user_id: int) -> Optional[tuple]:
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute(
"SELECT xp, notifications, notify_time FROM users WHERE user_id=?",
(user_id,)
) as cur:
return await cur.fetchone()
async def db_update_user(user_id: int, **kwargs):
set_clause = ", ".join(f"{k}=?" for k in kwargs.keys())
async with aiosqlite.connect(DB_NAME) as db:
await db.execute(f"UPDATE users SET {set_clause} WHERE user_id=?", (*kwargs.values(), user_id))
await db.commit()
# ==================== HELPERS ====================
RANKS = [
(0, "Новичок", "🌱"),
(200, "Ученик", "📚"),
(500, "Крепкий орешек", "🥜"),
(1000, "Машина", "⚙️"),
(2000, "Легенда", "👑"),
(5000, "Абсолют", "🌟"),
]
async def get_rank(xp: int) -> tuple[str, int]:
for threshold, name, emoji in reversed(RANKS):
if xp >= threshold:
return f"{emoji} {name}", threshold
return "🌱 Новичок", 0
def main_menu_kb():
return ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="📊 Оценки"), KeyboardButton(text="📅 Расписание"), KeyboardButton(text="📚 Домашка")],
[KeyboardButton(text="🏆 Профиль"), KeyboardButton(text="⚙️ Настройки"), KeyboardButton(text="🔔 Напомни")]
],
resize_keyboard=True,
input_field_placeholder="Выберите действие"
)
def cancel_kb():
return ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="⬅️ Назад")]], resize_keyboard=True)
def back_kb():
return ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="⬅️ Назад")]], resize_keyboard=True)
async def delete_prev_messages(bot: Bot, user_id: int, state: FSMContext):
data = await state.get_data()
if "message_ids" in data:
for msg_id in data["message_ids"]:
try:
await bot.delete_message(user_id, msg_id)
except:
pass
await state.update_data(message_ids=[])
def days_kb():
days = [("ПН", "Понедельник"), ("ВТ", "Вторник"), ("СР", "Среда"),
("ЧТ", "Четверг"), ("ПТ", "Пятница"), ("СБ", "Суббота")]
kb = []
for i in range(0, len(days), 2):
row = [InlineKeyboardButton(text=days[i][1], callback_data=f"day_{days[i][0]}")]
if i + 1 < len(days):
row.append(InlineKeyboardButton(text=days[i+1][1], callback_data=f"day_{days[i+1][0]}"))
kb.append(row)
kb.append([InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")])
return InlineKeyboardMarkup(inline_keyboard=kb)
def settings_kb(notif_status: str, notify_time: str):
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=f'🔔 Уведомления: {notif_status}', callback_data="set_notifications")],
[InlineKeyboardButton(text=f"⏰ Время: {notify_time}", callback_data="set_time")],
[InlineKeyboardButton(text="🔄 Сброс данных", callback_data="set_reset")],
[InlineKeyboardButton(text="ℹ️ О боте", callback_data="set_info")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
])
# ==================== BACKGROUND TASKS ====================
async def check_deadlines(bot: Bot):
async with aiosqlite.connect(DB_NAME) as db:
now = datetime.now()
async with db.execute(
"SELECT h.id, h.user_id, h.subject, h.deadline, h.notify_level, u.notifications "
"FROM homework h JOIN users u ON h.user_id = u.user_id WHERE h.is_done=0"
) as cursor:
async for row in cursor:
hw_id, uid, sub, dl_str, n_level, notif_on = row
if not notif_on:
continue
try:
dl = datetime.strptime(dl_str, "%Y-%m-%d %H:%M")
hours_left = (dl - now).total_seconds() / 3600
msg, new_level = None, n_level
if 0 < hours_left <= 2 and n_level < 3:
msg, new_level = f"🔥 СРОЧНО: {sub} через 2ч!", 3
elif 0 < hours_left <= 12 and n_level < 2:
msg, new_level = f"⏰ До {sub} осталось 12ч", 2
elif 0 < hours_left <= 24 and n_level < 1:
msg, new_level = f"📝 {sub}", 1
if msg:
await bot.send_message(uid, msg)
await db.execute("UPDATE homework SET notify_level=? WHERE id=?", (new_level, hw_id))
except Exception as e:
logger.error(f"Ошибка дедлайна {hw_id}: {e}")
await db.commit()
async def check_reminders(bot: Bot):
now = datetime.now().strftime("%Y-%m-%d %H:%M")
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute("SELECT id, user_id, text FROM reminders WHERE remind_at <= ?", (now,)) as cur:
rows = await cur.fetchall()
for r in rows:
await bot.send_message(r[1], f"🔔 Напоминание: {r[2]}")
await db.execute("DELETE FROM reminders WHERE id=?", (r[0],))
await db.commit()
# ==================== COMMAND HANDLERS ====================
@router.message(Command("start"))
async def cmd_start(m: Message):
async with aiosqlite.connect(DB_NAME) as db:
await db.execute("INSERT OR IGNORE INTO users (user_id) VALUES (?)", (m.from_user.id,))
await db.commit()
await m.answer(
f'📚 Привет, {m.from_user.first_name}!\n\n'
"Я SiOK — твой помощник по учебе!\n"
"/help\n"
"\n"
'📚 Что я умею:\n'
'• 📊 Оценки — добавляй и отслеживай\n'
'• 📅 Расписание — планируй неделю\n'
'• 📚 Домашка — не забывай о заданиях\n'
'• 📚 Напоминания — установи будильник\n'
'• 📚 XP — получай опыт за дела!',
parse_mode="HTML",
reply_markup=main_menu_kb()
)
@router.message(Command("help"))
async def cmd_help(m: Message):
await m.answer(
'📖 Справка:\n\n'
'Команды:\n'
'• /start — Запуск\n'
'• /menu — Показать меню\n'
'• /stats — Статистика\n'
'• /help — Справка\n\n'
'Меню:\n'
'• 📊 Оценки\n'
'• 📅 Расписание\n'
'• 📚 Домашка\n'
'• 📚 Профиль\n'
'• ⚙️ Настройки\n'
'• 📚 Напомни',
parse_mode="HTML"
)
@router.message(Command("menu"))
async def cmd_menu(m: Message):
await m.answer('📚 Главное меню:', reply_markup=main_menu_kb(),
parse_mode='HTML')
@router.message(Command("stats"))
async def cmd_stats(m: Message):
user_id = m.from_user.id
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute("SELECT COUNT(*) FROM grades WHERE user_id=?", (user_id,)) as c:
grades_count = (await c.fetchone())[0] or 0
async with db.execute("SELECT COUNT(*) FROM homework WHERE user_id=? AND is_done=1", (user_id,)) as c:
hw_done = (await c.fetchone())[0] or 0
async with db.execute("SELECT COUNT(*) FROM homework WHERE user_id=? AND is_done=0", (user_id,)) as c:
hw_pending = (await c.fetchone())[0] or 0
user = await db_get_user(user_id)
xp = user[0] if user and user[0] else 0
rank, _ = await get_rank(xp)
await m.answer(
f'📚 Твоя статистика:\n\n'
f'📚 XP: {xp} | {rank}\n\n'
f'📚 Выполнено: {hw_done}\n'
f"📋 Осталось: {hw_pending}\n"
f"📊 Оценок: {grades_count}",
parse_mode="HTML"
)
# ==================== CANCEL / BACK ====================
@router.message(F.text == "⬅️ Назад")
async def back_handler(m: Message, state: FSMContext):
await state.clear()
await m.answer("Главное меню:", reply_markup=main_menu_kb())
@router.callback_query(F.data == "back_to_menu")
async def back_to_menu(c: CallbackQuery, state: FSMContext):
try:
await c.message.delete()
except:
pass
await c.message.answer("Главное меню:", reply_markup=main_menu_kb())
await c.answer()
@router.callback_query(F.data == "back_to_schedule")
async def back_to_schedule(c: CallbackQuery):
try:
await c.message.edit_text(
'📚 Выберите день:',
reply_markup=days_kb(),
parse_mode='HTML'
)
except:
pass
await c.answer()
# ==================== GRADES ====================
@router.message(F.text == "📊 Оценки")
async def grades_main(m: Message, state: FSMContext):
await state.update_data(message_ids=[m.message_id])
await m.answer(
'📊 Оценки',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить", callback_data="grade_add"),
InlineKeyboardButton(text="📊 Средний балл", callback_data="grade_avg")],
[InlineKeyboardButton(text="🗑 Сбросить", callback_data="grade_reset")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
@router.callback_query(F.data == "grade_add")
async def grade_add(c: CallbackQuery, state: FSMContext):
await state.set_state(GradeState.subject)
await state.update_data(msg_id=c.message.message_id)
await c.message.edit_text(
"Введите название предмета:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_grades")]
])
)
await c.answer()
@router.callback_query(F.data == "back_to_grades")
async def back_to_grades(c: CallbackQuery, state: FSMContext):
await state.clear()
await c.message.edit_text(
'📊 Оценки',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить", callback_data="grade_add"),
InlineKeyboardButton(text="📊 Средний балл", callback_data="grade_avg")],
[InlineKeyboardButton(text="🗑 Сбросить", callback_data="grade_reset")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
await c.answer()
@router.message(GradeState.subject)
async def grade_sub(m: Message, state: FSMContext):
await state.update_data(sub=m.text)
await state.set_state(GradeState.value)
try:
await m.delete()
data = await state.get_data()
if "msg_id" in data:
await m.bot.edit_message_text(
"Введите оценку (2-5):",
chat_id=m.from_user.id,
message_id=data["msg_id"],
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_grades")]
])
)
except:
pass
@router.message(GradeState.value)
async def grade_val(m: Message, state: FSMContext):
if not m.text.isdigit() or not (2 <= int(m.text) <= 5):
return await m.answer("Введите цифру от 2 до 5!")
data = await state.get_data()
async with aiosqlite.connect(DB_NAME) as db:
await db.execute("INSERT INTO grades (user_id, subject, grade) VALUES (?, ?, ?)",
(m.from_user.id, data['sub'], int(m.text)))
await db.commit()
await state.clear()
try:
await m.delete()
if "msg_id" in data:
await m.bot.edit_message_text(
"✅ Оценка сохранена!",
chat_id=m.from_user.id,
message_id=data["msg_id"]
)
await m.bot.send_message(
m.from_user.id,
'📊 Оценки',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить", callback_data="grade_add"),
InlineKeyboardButton(text="📊 Средний балл", callback_data="grade_avg")],
[InlineKeyboardButton(text="🗑 Сбросить", callback_data="grade_reset")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode='HTML'
)
except:
pass
@router.callback_query(F.data == "grade_avg")
async def grade_avg(c: CallbackQuery, state: FSMContext):
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute(
"SELECT subject, AVG(grade) FROM grades WHERE user_id=? GROUP BY subject",
(c.from_user.id,)
) as cur:
res = await cur.fetchall()
if not res:
return await c.answer("Оценок пока нет", show_alert=True)
text = '📚 Средние баллы:\n' + "\n".join([f"• {r[0]}: {r[1]:.2f}" for r in res])
await state.update_data(avg_msg_id=c.message.message_id)
await c.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_grades")]
]),
parse_mode="HTML"
)
await c.answer()
@router.callback_query(F.data == "grade_reset")
async def grade_reset(c: CallbackQuery):
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute("SELECT COUNT(*) FROM grades WHERE user_id=?", (c.from_user.id,)) as cur:
count = (await cur.fetchone())[0]
if count == 0:
await c.answer("Оценок пока нет", show_alert=True)
return
await c.message.edit_text(
"⚠️ Вы точно хотите сбросить все оценки?",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="✅ Да", callback_data="grade_confirm_reset"),
InlineKeyboardButton(text="❌ Нет", callback_data="back_to_grades")]
])
)
await c.answer()
@router.callback_query(F.data == "grade_confirm_reset")
async def grade_confirm_reset(c: CallbackQuery, state: FSMContext):
async with aiosqlite.connect(DB_NAME) as db:
await db.execute("DELETE FROM grades WHERE user_id=?", (c.from_user.id,))
await db.commit()
await c.message.edit_text("✅ Оценки сброшены!")
data = await state.get_data()
if "avg_msg_id" in data:
try:
await c.bot.edit_message_text(
'📊 Оценки',
chat_id=c.from_user.id,
message_id=data["avg_msg_id"],
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить", callback_data="grade_add"),
InlineKeyboardButton(text="📊 Средний балл", callback_data="grade_avg")],
[InlineKeyboardButton(text="🗑 Сбросить", callback_data="grade_reset")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
except:
pass
else:
await c.message.answer(
'📊 Оценки',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить", callback_data="grade_add"),
InlineKeyboardButton(text="📊 Средний балл", callback_data="grade_avg")],
[InlineKeyboardButton(text="🗑 Сбросить", callback_data="grade_reset")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
await c.answer()
# ==================== SCHEDULE ====================
@router.message(F.text == "📅 Расписание")
async def sch_main(m: Message, state: FSMContext):
await state.update_data(message_ids=[m.message_id])
await m.answer('📚 Выберите день:', reply_markup=days_kb(), parse_mode='HTML')
@router.callback_query(F.data.startswith("day_"))
async def sch_day(c: CallbackQuery):
day = c.data.split("_")[1]
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute(
"SELECT tasks FROM schedule WHERE user_id=? AND day=?", (c.from_user.id, day)
) as cur:
row = await cur.fetchone()
text = row[0] if row else "На этот день ничего не записано."
await c.message.edit_text(
f'📚Расписание на {day}:\n\n{text}',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="📝 Редактировать", callback_data=f"edit_{day}")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_schedule")]
]),
parse_mode="HTML"
)
await c.answer()
@router.callback_query(F.data.startswith("edit_"))
async def sch_edit(c: CallbackQuery, state: FSMContext):
day = c.data.split("_")[1]
await state.update_data(day=day, edit_msg_id=c.message.message_id)
await state.set_state(ScheduleState.text)
await c.message.edit_text(
f"Введите расписание для {day}:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_schedule")]
])
)
await c.answer()
@router.message(ScheduleState.text)
async def sch_save(m: Message, state: FSMContext):
data = await state.get_data()
async with aiosqlite.connect(DB_NAME) as db:
await db.execute(
"INSERT OR REPLACE INTO schedule (user_id, day, tasks) VALUES (?, ?, ?)",
(m.from_user.id, data['day'], m.text)
)
await db.commit()
await state.clear()
try:
await m.delete()
if "edit_msg_id" in data:
await m.bot.delete_message(m.from_user.id, data["edit_msg_id"])
except:
pass
await m.answer("✅ Расписание сохранено!")
await m.answer('📚 Выберите день:', reply_markup=days_kb(), parse_mode='HTML')
# ==================== HOMEWORK ====================
@router.message(F.text == "📚 Домашка")
async def hw_main(m: Message, state: FSMContext):
await state.update_data(message_ids=[m.message_id])
await m.answer(
'📚 Домашка',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить ДЗ", callback_data="hw_add"),
InlineKeyboardButton(text="📋 Список ДЗ", callback_data="hw_list")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
@router.callback_query(F.data == "hw_add")
async def hw_add(c: CallbackQuery, state: FSMContext):
await state.set_state(HWState.subject)
await state.update_data(msg_id=c.message.message_id)
await c.message.edit_text(
"Введите предмет:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_homework")]
])
)
await c.answer()
@router.callback_query(F.data == "back_to_homework")
async def back_to_homework(c: CallbackQuery, state: FSMContext):
await state.clear()
await c.message.edit_text(
'📚 Домашка',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить ДЗ", callback_data="hw_add"),
InlineKeyboardButton(text="📋 Список ДЗ", callback_data="hw_list")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
await c.answer()
@router.message(HWState.subject)
async def hw_sub(m: Message, state: FSMContext):
await state.update_data(sub=m.text)
await state.set_state(HWState.desc)
try:
await m.delete()
data = await state.get_data()
if "msg_id" in data:
await m.bot.edit_message_text(
"Что задали?",
chat_id=m.from_user.id,
message_id=data["msg_id"],
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_homework")]
])
)
except:
pass
@router.message(HWState.desc)
async def hw_desc(m: Message, state: FSMContext):
await state.update_data(desc=m.text)
await state.set_state(HWState.deadline)
try:
await m.delete()
data = await state.get_data()
if "msg_id" in data:
await m.bot.edit_message_text(
"Дедлайн (ГГГГ-ММ-ДД ЧЧ:ММ)\nПример: 2026-05-20 14:00",
chat_id=m.from_user.id,
message_id=data["msg_id"],
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_homework")]
])
)
except:
pass
@router.message(HWState.deadline)
async def hw_dl(m: Message, state: FSMContext):
try:
datetime.strptime(m.text, "%Y-%m-%d %H:%M")
except ValueError:
return await m.answer("❌ Ошибка! Пример: 2026-05-20 14:00")
data = await state.get_data()
async with aiosqlite.connect(DB_NAME) as db:
await db.execute(
"INSERT INTO homework (user_id, subject, desc, deadline) VALUES (?, ?, ?, ?)",
(m.from_user.id, data['sub'], data['desc'], m.text)
)
await db.commit()
await state.clear()
try:
await m.delete()
if "msg_id" in data:
await m.bot.edit_message_text(
"✅ Задание добавлено!",
chat_id=m.from_user.id,
message_id=data["msg_id"]
)
await m.bot.send_message(
m.from_user.id,
'📚 Домашка',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Добавить ДЗ", callback_data="hw_add"),
InlineKeyboardButton(text="📋 Список ДЗ", callback_data="hw_list")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
except:
pass
@router.callback_query(F.data == "hw_list")
async def hw_list(c: CallbackQuery, state: FSMContext):
async with aiosqlite.connect(DB_NAME) as db:
async with db.execute(
"SELECT id, subject, desc, deadline FROM homework WHERE user_id=? AND is_done=0",
(c.from_user.id,)
) as cur:
rows = await cur.fetchall()
if not rows:
return await c.answer("Заданий нет!", show_alert=True)
for r in rows:
await c.message.answer(
f'📚 {r[1]}\n📝 {r[2]}\n⏰ До: {r[3]}',
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="✅ Сделано", callback_data=f"done_{r[0]}")]
]),
parse_mode="HTML"
)
await c.answer()
@router.callback_query(F.data.startswith("done_"))
async def hw_done(c: CallbackQuery):
hw_id = c.data.split("_")[1]
await c.message.edit_text(
"Оцени сложность:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="😊 Легко (+10 XP)", callback_data=f"xp_{hw_id}_10")],
[InlineKeyboardButton(text="😐 Нормально (+20 XP)", callback_data=f"xp_{hw_id}_20")],
[InlineKeyboardButton(text="😞 Сложно (+30 XP)", callback_data=f"xp_{hw_id}_30")]
])
)
@router.callback_query(F.data.startswith("xp_"))
async def add_xp(c: CallbackQuery):
_, hw_id, amount = c.data.split("_")
amount = int(amount)
async with aiosqlite.connect(DB_NAME) as db:
await db.execute("UPDATE homework SET is_done=1 WHERE id=?", (hw_id,))
await db.execute("UPDATE users SET xp = xp + ? WHERE user_id=?", (amount, c.from_user.id))
await db.commit()
rank, _ = await get_rank(amount)
await c.message.edit_text(f"🎉 +{amount} XP\n🎖 {rank}")
await c.answer()
# ==================== PROFILE ====================
@router.message(F.text == "🏆 Профиль")
async def profile(m: Message):
user = await db_get_user(m.from_user.id)
xp = user[0] if user and user[0] else 0
rank, threshold = await get_rank(xp)
next_rank = next((r[1] for r in RANKS if r[0] > threshold), "Максимум!")
await m.answer(
f"👤 Твой профиль\n\n"
f'📚 XP: {xp}\n'
f'📚 Ранг: {rank}\n\n'
f"📈 До следующего: {next_rank}",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]),
parse_mode="HTML"
)
# ==================== SETTINGS ====================
@router.message(F.text == "⚙️ Настройки")
async def settings(m: Message, state: FSMContext):
await state.update_data(message_ids=[m.message_id])
async with aiosqlite.connect(DB_NAME) as db:
await db.execute("INSERT OR IGNORE INTO users (user_id) VALUES (?)", (m.from_user.id,))
await db.commit()
user = await db_get_user(m.from_user.id)
notif_status = "✅ Вкл" if (user and user[1]) else "❌ Выкл"
notify_time = user[2] if user and user[2] else "09:00"
await m.answer(
"⚙️ Настройки",
reply_markup=settings_kb(notif_status, notify_time),
parse_mode="HTML"
)
@router.callback_query(F.data == "set_notifications")
async def set_notifications(c: CallbackQuery):
await c.message.answer(
"🔔 Уведомления:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="✅ Включить", callback_data="notif_on"),
InlineKeyboardButton(text="❌ Выключить", callback_data="notif_off")]
])
)
await c.answer()
@router.callback_query(F.data == "notif_on")
async def notif_on(c: CallbackQuery):
await db_update_user(c.from_user.id, notifications=1)
await c.message.answer("✅ Уведомления включены!")
await c.answer()
@router.callback_query(F.data == "notif_off")
async def notif_off(c: CallbackQuery):
await db_update_user(c.from_user.id, notifications=0)
await c.message.answer("❌ Уведомления выключены.")
await c.answer()
@router.callback_query(F.data == "set_time")
async def set_time_start(c: CallbackQuery, state: FSMContext):
await state.set_state(NotifyTimeState.time)
try:
await c.message.delete()
except:
pass
await c.message.answer("⏰ Введите время (ЧЧ:ММ)\nНапример: 09:00", reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_settings")]
]))
await c.answer()
@router.callback_query(F.data == "back_to_settings")
async def back_to_settings(c: CallbackQuery, state: FSMContext):
await state.clear()
try:
await c.message.delete()
except:
pass
user = await db_get_user(c.from_user.id)
notif_status = "✅ Вкл" if (user and user[1]) else "❌ Выкл"
notify_time = user[2] if user and user[2] else "09:00"
await c.message.answer(
"⚙️ Настройки",
reply_markup=settings_kb(notif_status, notify_time),
parse_mode="HTML"
)
await c.answer()
@router.message(NotifyTimeState.time)
async def set_time_save(m: Message, state: FSMContext):
try:
parts = m.text.split(":")
hour, minute = int(parts[0]), int(parts[1])
if not (0 <= hour <= 23 and 0 <= minute <= 59):
raise ValueError()
time_str = f"{hour:02d}:{minute:02d}"
except:
return await m.answer("❌ Ошибка! Пример: 09:00")
await db_update_user(m.from_user.id, notify_time=time_str)
await state.clear()
await m.answer(f"✅ Время: {time_str}")
await m.answer("Главное меню:", reply_markup=main_menu_kb())
@router.callback_query(F.data == "set_info")
async def set_info(c: CallbackQuery):
await c.message.answer(
"ℹ️ О боте SiOK\n\n"
"Версия: 2.0.0\n\n"
"📚 Возможности:\n"
"• Оценки и средний балл\n"
"• Расписание на неделю\n"
"• Домашка + уведомления\n"
"• Напоминания\n"
"• Система XP и рангов\n\n"
"© 2026 SiOK Bot",
parse_mode="HTML"
)
await c.answer()
@router.callback_query(F.data == "set_reset")
async def set_reset(c: CallbackQuery):
await c.message.answer(
"⚠️ ВНИМАНИЕ!\n\n"
"Это удалит ВСЕ данные:\n"
"• Оценки\n"
"• Расписание\n"
"• Домашка\n"
"• Прогресс\n\n"
"Вы уверены?",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⚠️ Да, сбросить", callback_data="confirm_reset")],
[InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_reset")]
]),
parse_mode="HTML"
)
await c.answer()
@router.callback_query(F.data == "confirm_reset")
async def confirm_reset(c: CallbackQuery):
async with aiosqlite.connect(DB_NAME) as db:
await db.execute("DELETE FROM grades WHERE user_id=?", (c.from_user.id,))
await db.execute("DELETE FROM schedule WHERE user_id=?", (c.from_user.id,))
await db.execute("DELETE FROM homework WHERE user_id=?", (c.from_user.id,))
await db.execute("UPDATE users SET xp=0 WHERE user_id=?", (c.from_user.id,))
await db.commit()
await c.message.answer("✅ Данные сброшены!")
await c.answer()
@router.callback_query(F.data == "cancel_reset")
async def cancel_reset(c: CallbackQuery):
await c.message.answer("✅ Отменено.")
await c.answer()
# ==================== REMIND ====================
@router.message(F.text == "🔔 Напомни")
async def remind_start(m: Message, state: FSMContext):
await state.update_data(message_ids=[m.message_id])
await m.answer('📚 О чём напомнить?', reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")]
]), parse_mode='HTML')
await state.set_state(RemindState.text)
@router.message(RemindState.text)
async def remind_text(m: Message, state: FSMContext):
await state.update_data(text=m.text)
await m.answer("⏰ Введите дату и время:\nГГГГ-ММ-ДД ЧЧ:ММ\nПример: 2026-02-20 15:30")
await state.set_state(RemindState.time)
@router.message(RemindState.time)
async def remind_save(m: Message, state: FSMContext):
try:
remind_at = datetime.strptime(m.text.strip(), "%Y-%m-%d %H:%M")
except ValueError:
return await m.answer("❌ Ошибка! Пример: 2026-02-20 15:30")
if remind_at <= datetime.now():
return await m.answer("❌ Время должно быть в будущем!")
data = await state.get_data()
async with aiosqlite.connect(DB_NAME) as db:
await db.execute(
"INSERT INTO reminders (user_id, text, remind_at) VALUES (?, ?, ?)",
(m.from_user.id, data['text'], m.text.strip())
)
await db.commit()
await state.clear()
await m.answer(f"✅ Напоминание установлено!\n\n🔔 {data['text']}\n⏰ {m.text}")
await m.answer("Главное меню:", reply_markup=main_menu_kb())
# ==================== MAIN ====================
async def main():
await init_db()
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher(storage=MemoryStorage())
dp.include_router(router)
scheduler = AsyncIOScheduler()
scheduler.add_job(check_deadlines, "interval", minutes=5, args=[bot])
scheduler.add_job(check_reminders, "interval", minutes=1, args=[bot])
scheduler.start()
try:
await dp.start_polling(bot)
finally:
await bot.session.close()
if __name__ == "__main__":
try:
asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
logger.info("Бот выключен")