Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@ CREATE TABLE users (
sleep_time TIME DEFAULT NULL
);

-- TABLE routines --
CREATE TABLE routines (
id SERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
routine_type routine_type NOT NULL,
routine_name VARCHAR(255) NOT NULL
);

-- TABLE Focuses --
CREATE TABLE focuses (
id SERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
duration VARCHAR(255) NOT NULL,
title VARCHAR(225),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

-- TABLE tasks
CREATE TABLE tasks (
id SERIAL PRIMARY KEY,
Expand Down
24 changes: 20 additions & 4 deletions telegram_bot_project/bot/buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,13 @@ def evening_routine_keyboard() -> ReplyKeyboardMarkup:
def focus_menu_keyboard(started: Optional[bool] = False) -> ReplyKeyboardMarkup:
menu_btn = KeyboardButton(text=MENU_BUTTON)
focus_btn = KeyboardButton(text=FOCUS_ZONE_END if started else FOCUS_ZONE_START)
focus_list = KeyboardButton(text=ALL_FOCUSES_BTN)
delete_focus = KeyboardButton(text=DELETE_FOCUS)

keyboard = [
[focus_btn],
[focus_list],
[delete_focus],
[menu_btn]
]

Expand All @@ -173,9 +177,21 @@ def focus_menu_keyboard(started: Optional[bool] = False) -> ReplyKeyboardMarkup:
def focus_save_question_keyboard() -> InlineKeyboardMarkup:
inline_markup = InlineKeyboardMarkup(inline_keyboard=[], row_width=2)

save_focus_btn: InlineKeyboardButton = InlineKeyboardButton(text=FOCUS_INLINE_YES, callback_data="save_focus")
not_save_focus_btn: InlineKeyboardButton = InlineKeyboardButton(text=FOCUS_INLINE_NO, callback_data="not_save_focus")
save_focus_btn = InlineKeyboardButton(text=FOCUS_INLINE_YES, callback_data="save_focus")
not_save_focus_btn = InlineKeyboardButton(text=FOCUS_INLINE_NO, callback_data="not_save_focus")

inline_markup.inline_keyboard.append([save_focus_btn, not_save_focus_btn])

inline_markup.add(save_focus_btn)
inline_markup.add(not_save_focus_btn)
return inline_markup


def focus_title_ask_keyboard() -> InlineKeyboardMarkup:
inline_markup = InlineKeyboardMarkup(inline_keyboard=[], row_width=2)

add_title = InlineKeyboardButton(text=FOCUS_INLINE_YES, callback_data="add_title")
not_add_title = InlineKeyboardButton(text=FOCUS_INLINE_NO, callback_data="not_add_title")

inline_markup.inline_keyboard.append([add_title, not_add_title])

return inline_markup

52 changes: 51 additions & 1 deletion telegram_bot_project/bot/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from service.task import TaskService
from service.user import UserService
from states import DialogStates
from service.focus import FocusService

async def start_callback_language(callback_query: types.CallbackQuery) -> None:
await callback_query.answer()
Expand Down Expand Up @@ -129,4 +130,53 @@ async def callback_routines(callback_query: types.CallbackQuery) -> None:
case "evening_view":
await callback_query.message.answer(MESSAGES[language]['EVENING_ROUTINE'], reply_markup=evening_routine_keyboard())
case _:
await callback_query.message.answer(MESSAGES[language]["ROUTINES_INVALID"])
await callback_query.message.answer(MESSAGES[language]["ROUTINES_INVALID"])

async def callback_focus(callback_query: types.CallbackQuery, state: FSMContext) -> None:
await callback_query.answer()

user_id: int = callback_query.from_user.id
user_find: Optional[dict] = await UserService.get_user_by_id(user_id)
language: str = await UserService.get_user_language(user_id)
if not language:
language = 'ENGLISH'
if not user_find:
await callback_query.message.answer(MESSAGES['ENGLISH']["AUTHORIZATION_PROBLEM"])
return

match callback_query.data:
case "save_focus":
await callback_query.message.answer(MESSAGES[language]["TITLE_FOCUS_ZONE_MSG"], reply_markup=focus_title_ask_keyboard())
case "not_save_focus":
await callback_query.message.answer(MESSAGES[language]["NOT_SAVED_FOCUS_MSG"], reply_markup=focus_menu_keyboard())
case _:
await callback_query.message.answer(MESSAGES[language]["FOCUS_INVALID"], reply_markup=focus_menu_keyboard())

async def callback_focus_title(callback_query: types.CallbackQuery, state: FSMContext) -> None:
await callback_query.answer()

user_id: int = callback_query.from_user.id
user_find: Optional[dict] = await UserService.get_user_by_id(user_id)
language: str = await UserService.get_user_language(user_id)
if not language:
language = 'ENGLISH'
if not user_find:
await callback_query.message.answer(MESSAGES['ENGLISH']["AUTHORIZATION_PROBLEM"])
return

match callback_query.data:
case "add_title":
await state.set_state(DialogStates.provide_title_focusing)
await callback_query.message.answer(MESSAGES[language]["FOCUS_TITLE_ASK"])
case "not_add_title":
print(f"[INFO] - User {user_id} canceled adding title.")

data = await state.get_data()
time_d = data.get("duration")
await FocusService.create_focus(user_id, duration=time_d)

print(f"[INFO] - User {user_id} saved focus: {time_d}")
await callback_query.message.answer(MESSAGES[language]["SAVED_FOCUS_MSG"], reply_markup=focus_menu_keyboard())
await state.clear()
case _:
await callback_query.message.answer(MESSAGES[language]["FOCUS_INVALID"], reply_markup=focus_menu_keyboard())
54 changes: 52 additions & 2 deletions telegram_bot_project/bot/commands.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# bot/commands.py
from typing import List
from aiogram.fsm.context import FSMContext

from service.focus import FocusService
from bot.utills import format_date, calculate_awake_hours
from service.idea import IdeaService
from service.task import TaskService
Expand Down Expand Up @@ -446,4 +446,54 @@ async def show_focus_menu(message: types.Message) -> None:
await message.answer(MESSAGES['ENGLISH']['AUTHORIZATION_PROBLEM'])
else:
print(f"[INFO] - User with id: {user_id} - opened /focus.")
await message.answer(MESSAGES[language]['WELCOME_TO_FOCUS'], reply_markup=focus_menu_keyboard())
await message.answer(MESSAGES[language]['WELCOME_TO_FOCUS'], reply_markup=focus_menu_keyboard())

# Show All Focus Sessions Handler
async def show_all_focuses(message: types.Message) -> None:
user_id: int = message.from_user.id
user_find: Any = await UserService.get_user_by_id(user_id)
language: str = await UserService.get_user_language(user_id) or "ENGLISH"

if not user_find:
await message.answer(MESSAGES[language]['AUTHORIZATION_PROBLEM'])
return

print(f"[INFO] - User with id: {user_id} - opened /show_all_focuses.")
focuses = await FocusService.get_focuses_by_user(user_id)

if not focuses:
await message.answer(MESSAGES[language]['NO_FOCUS_SESSIONS'])
return

dividers: str = "\n" + ("-" * int(len(MESSAGES[language]['FOCUS_LIST_TITLE']) * 1.65))
formatted_focuses = "\n".join(
f"# {idx}. {focus['title']} ({focus['duration']}) – {focus['created_at'].strftime('%Y-%m-%d %H:%M')}"
for idx, focus in enumerate(focuses, start=1)
)

formatted_response = (
MESSAGES[language]['FOCUS_LIST_TITLE'] +
dividers +
"\n" +
formatted_focuses
)

await message.answer(formatted_response, reply_markup=focus_menu_keyboard())

# Delete Focus Session Handler
async def delete_focus_session(message: types.Message, state: FSMContext) -> None:
user_id: int = message.from_user.id
user_find: Any = await UserService.get_user_by_id(user_id)
language: str = await UserService.get_user_language(user_id) or "ENGLISH"

if not user_find:
await message.answer(MESSAGES['ENGLISH']['AUTHORIZATION_PROBLEM'])
else:
focuses = await FocusService.get_focuses_by_user(user_id)
if not focuses:
await message.answer(MESSAGES[language]['NO_FOCUS_SESSIONS'])
return

await state.update_data(focuses=focuses)
await state.set_state(DialogStates.delete_focus)
await message.answer(MESSAGES[language]['DELETE_FOCUS_SESSION_MSG'])
6 changes: 5 additions & 1 deletion telegram_bot_project/bot/fallbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ async def fallback(message: types.Message, state: FSMContext):
routine_type = data.get("routine_type", "morning")
await process_update_morning_routine(message, state, type=routine_type)
elif current_state == DialogStates.feedback_message:
await process_feedback_message(message, state)
await process_feedback_message(message, state)
elif current_state == DialogStates.provide_title_focusing:
await process_save_focus_session_title(message, state)
elif current_state == DialogStates.delete_focus:
await process_delete_focus_session(message, state)
59 changes: 58 additions & 1 deletion telegram_bot_project/bot/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from bot.utills import check_valid_time, validate_text
from service.myday import MyDayService
from bot.scheduler import update_user_job
from service.focus import FocusService

async def process_idea_save(message: Message, state: FSMContext) -> None:
user_id = message.from_user.id
Expand Down Expand Up @@ -500,4 +501,60 @@ async def process_feedback_message(message: Message, state: FSMContext):
print(f"[INFO] - Feedback message from user with id: {user_id} is: {feedback_message}")
await SmtpService.send_feedback_message(feedback_message, user_id, user_name)
await message.answer(MESSAGES[language]['SMTP_MESSAGE_SENT'], reply_markup=settings_menu_keyboard())
await state.clear()
await state.clear()

async def process_save_focus_session_title(message: Message, state: FSMContext):
user_id = message.from_user.id
user_find = await UserService.get_user_by_id(user_id)
language = await UserService.get_user_language(user_id) or "ENGLISH"

if not user_find:
await message.answer(MESSAGES["ENGLISH"]['AUTHORIZATION_PROBLEM'])
return

focus_title: str = message.text.strip()
if not validate_text(focus_title):
await message.answer(MESSAGES[language]['FOCUS_INVALID'])
return

data = await state.get_data()
time_d = data.get("duration")

try:
await FocusService.create_focus(user_id, time_d, focus_title)

print(f"[INFO] - User with id: {user_id} created focus session with title: {focus_title}")
await message.answer(MESSAGES[language]['SAVED_FOCUS_MSG'].format(focus_title), reply_markup=focus_menu_keyboard())
await state.clear()
except Exception as e:
print(f"[ERROR] - {e}")
await message.answer(MESSAGES[language]['FOCUS_INVALID'])

async def process_delete_focus_session(message: Message, state: FSMContext):
user_id = message.from_user.id
user_find = await UserService.get_user_by_id(user_id)
language = await UserService.get_user_language(user_id) or "ENGLISH"

if not user_find:
await message.answer(MESSAGES["ENGLISH"]['AUTHORIZATION_PROBLEM'])
return

number = message.text.strip()
if not number.isdigit():
await message.answer(MESSAGES[language]['FOCUS_INVALID'])
return

data = await state.get_data()
focus_sessions = data.get("focuses")

try:
focus_to_delete = focus_sessions[int(number) - 1]
real_id = focus_to_delete["id"]

print(f"[INFO] - User with id: {user_id} deleted focus session with id: {real_id}")
await FocusService.delete_focus(real_id)
await message.answer(MESSAGES[language]['FOCUS_DELETED'].format(number, focus_to_delete['title']), reply_markup=focus_menu_keyboard())
await state.clear()
except Exception as e:
print(f"[ERROR] - {e}")
await message.answer(MESSAGES[language]['FOCUS_INVALID'])
28 changes: 24 additions & 4 deletions telegram_bot_project/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ async def focus_start(message: Message):


@dp.message(lambda m: m.text == FOCUS_ZONE_END)
async def focus_end(message: Message):
async def focus_end(message: Message, state: FSMContext):
user_id = message.from_user.id
user_find = await UserService.get_user_by_id(user_id)
language = await UserService.get_user_language(user_id)
Expand All @@ -207,7 +207,10 @@ async def focus_end(message: Message):
start_time = focus_times.get(user_id)

if not start_time:
await message.answer("❗ Не знайдено початку фокус-сесії.")
await message.answer(
MESSAGES[language]['NOT_FOUND_FOCUS_SESSION'],
reply_markup=focus_menu_keyboard(FOCUS_STATUS)
)
return

end_time = datetime.now()
Expand All @@ -219,12 +222,21 @@ async def focus_end(message: Message):

focus_times.pop(user_id, None)

await state.update_data(duration=f"{minutes}m:{seconds}s")
await message.answer(
MESSAGES[language]['STOP_FOCUS_MSG'].format(minutes, seconds),
reply_markup=focus_menu_keyboard(FOCUS_STATUS)
MESSAGES[language]['STOP_FOCUS_MSG'].format(minutes, seconds) +
'\n' + MESSAGES[language]['SAVE_FOCUS_ZONE'],
reply_markup=focus_save_question_keyboard()
)

@dp.message(Command("focuses"))
@dp.message(lambda m: m.text == ALL_FOCUSES_BTN)
async def show_saved_focus(message: Message):
await show_all_focuses(message)

@dp.message(lambda m: m.text == DELETE_FOCUS)
async def delete_focus(message: Message, state: FSMContext):
await delete_focus_session(message, state)

@dp.callback_query(F.data.in_({"morning_view", "evening_view"}))
async def callback_routine(callback_query: CallbackQuery):
Expand All @@ -242,6 +254,14 @@ async def callback_idea(callback_query: CallbackQuery, state: FSMContext):
async def callback_idea(callback_query: CallbackQuery, state: FSMContext):
await callback_task_deadline(callback_query, state)

@dp.callback_query(F.data.in_({"save_focus", "not_save_focus"}))
async def callback_focus_save(callback_query: CallbackQuery, state: FSMContext):
await callback_focus(callback_query, state)

@dp.callback_query(F.data.in_({"add_title", "not_add_title"}))
async def callback_focus_title_save(callback_query: CallbackQuery, state: FSMContext):
await callback_focus_title(callback_query, state)

@dp.message()
async def process_fallback(message: Message, state: FSMContext):
await fallback(message, state)
Expand Down
24 changes: 22 additions & 2 deletions telegram_bot_project/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,22 @@
"STOP_FOCUS_MSG": "Сесію фокусу зупинено.\nТривалість: - {}хв {}с. ⏳",
"SAVE_FOCUS_ZONE": "Зберегти сесію фокусу? 📝",
"SAVED_FOCUS_MSG": "Сесію фокусу збережено. ✅",
"NOT_SAVED_FOCUS_MSG": "Сесію фокусу не збережено. ❌",
"TITLE_FOCUS_ZONE_MSG": "Бажаєте дати назву цій сесії? 📝",
"NOT_FOUND_FOCUS_SESSION": "❗ Не знайдено початку фокус-сесії.",
"FOCUS_INVALID": "❗ Неправильний параметр фокусування",
"FOCUS_TITLE_ASK": "Будь ласка, введіть назву фокус-сесії.",
"FOCUS_EXISTS": "❗ Фокус-сесія вже активна.",
"FOCUS_LIST_TITLE": "🧠 Список ваших фокус-сесій",
"NO_FOCUS_SESSIONS": "😕 У вас ще немає жодної фокус-сесії.",
"LANGUAGE_ASK": (
"🌐 Виберіть мову для роботи. \n"
"Оберіть опцію нижче. 📚"
),
"LANGUAGE_OK": "✅ Мову змінено. Готові продовжити? 🚀",
"LANGUAGE_INVALID": "❌ Некоректний вибір мови. Спробуйте ще раз. 🔢"
"LANGUAGE_INVALID": "❌ Некоректний вибір мови. Спробуйте ще раз. 🔢",
"DELETE_FOCUS_SESSION_MSG": "Вкажіть номер сесії, яку ви хочете видалити.",
"FOCUS_DELETED": "✅ Фокус-сесію №{} з назвою \"{}\" успішно видалено.",
},
"ENGLISH": {
"START_MSG": (
Expand Down Expand Up @@ -216,13 +225,22 @@
"STOP_FOCUS_MSG": "Focus session stopped.\nDuration - {}m {}s. ⏳",
"SAVE_FOCUS_ZONE": "Save the focus session? 📝",
"SAVED_FOCUS_MSG": "Focus session saved. ✅",
"NOT_SAVED_FOCUS_MSG": "Focus session not saved. ❌",
"TITLE_FOCUS_ZONE_MSG": "Would you like to name this session? 📝",
"NOT_FOUND_FOCUS_SESSION": "❗ Focus session start not found.",
"FOCUS_INVALID": "Invalid option for focus",
"FOCUS_TITLE_ASK": "Please provide the title for focus session.",
"FOCUS_EXISTS": "❗ A focus session is already active.",
"FOCUS_LIST_TITLE": "🧠 Your Focus Sessions",
"NO_FOCUS_SESSIONS": "😕 No focus sessions found.",
"DELETE_FOCUS_SESSION_MSG": "Provide a number of session which you want to delete.",
"LANGUAGE_ASK": (
"🌐 Choose your language. \n"
"Select an option below. 📚"
),
"LANGUAGE_OK": "✅ Language updated. Ready to proceed? 🚀",
"LANGUAGE_INVALID": "❌ Invalid language choice. Try again. 🔢"
"LANGUAGE_INVALID": "❌ Invalid language choice. Try again. 🔢",
"FOCUS_DELETED": "✅ Focus session #{} with the title \"{}\" has been successfully deleted.",
}
}

Expand Down Expand Up @@ -270,6 +288,8 @@
FOCUS_ZONE_END = "🔴 Stop"
FOCUS_INLINE_YES = "Yes"
FOCUS_INLINE_NO = "No"
ALL_FOCUSES_BTN = "All Focuses"
DELETE_FOCUS = "Delete"

USER_FEEDBACK_MAIL_TEXT = """
📬 New feedback received!
Expand Down
Loading