diff --git a/bot.py b/bot.py deleted file mode 100644 index ea50357..0000000 --- a/bot.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -import logging -from database.models import Students -from telebot import types -from datetime import datetime - -logger = logging.getLogger(__name__) - -authenticated_users = {2032936226} -_bot = None - -def get_bot(): - global _bot - if _bot is None: - import telebot - BOT_TOKEN = os.environ.get("BOT_TOKEN") - _bot = telebot.TeleBot(BOT_TOKEN, threaded=False) - - @_bot.message_handler(commands=["start"]) - def start(message): - if message.from_user.id in authenticated_users: - _bot.reply_to(message, "authorized user! Welcome. Use /getstudents to fetch from database.") - return - _bot.reply_to(message, "Welcome! Use /getstudents to fetch from database.") - - @_bot.message_handler(commands=["getstudents"]) - def get_data(message): - try: - data = Students.getAllStudents() - if data: - - markup = types.InlineKeyboardMarkup(row_width=2) - for row in data: - btn = types.InlineKeyboardButton(f"{row[0]}: @{row[1]}", callback_data=f"student_{row[0]}") - markup.add(btn) - - _bot.send_message(message.chat.id, "Here is the data:", reply_markup=markup) - else: - _bot.reply_to(message, "No data found.") - return - except Exception as e: - logger.error(f"Error in getdata handler: {e}") - _bot.reply_to(message, "Sorry, an error occurred.") - - # Single handler for all student selections - @_bot.callback_query_handler(func=lambda call: call.data.startswith('student_')) - def show_student_details(call): - student_id = call.data.split('_')[1] - student = Students.getStudentById(student_id) - - if student: - details = format_student_info(student) - markup = types.InlineKeyboardMarkup() - markup.add(types.InlineKeyboardButton("🔙 Back", callback_data="back_to_students")) - _bot.send_message(call.message.chat.id, details, reply_markup=markup, parse_mode="HTML") - else: - _bot.send_message(call.message.chat.id, "Student not found.") - - def format_student_info(student): - """ - Format student data into a nice message - student is a tuple: (id, username, created_at, email, phone_number, last_seen, is_verified, birthday) - """ - print(student); - id, username, created_at, email, phone_number, last_seen, is_verfied, birthday = student - - verified_status = "✅ Verified" if is_verfied else "❌ Not Verified" - - # Format dates nicely - created_date = format_date(created_at) - last_seen_date = format_date(last_seen) - - # Calculate age from birthday - age = calculate_age(birthday) - - details = f""" - 👤 Student Profile - - Username: {username} - Email: {email} - Phone: {phone_number} - Birthday: {birthday} (Age: {age}) - Account Status: {verified_status} - - 📅 Account Info - Joined: {created_date} - Last Seen: {last_seen_date} - ID: {id} - """ - return details.strip() - - def format_date(date_string): - """Convert database date to readable format""" - if not date_string: - return "N/A" - - try: - # If date_string is already a datetime object - if isinstance(date_string, datetime): - return date_string.strftime("%d %b %Y, %H:%M") - - # If it's a string, parse it first - date_obj = datetime.strptime(str(date_string), "%Y-%m-%d %H:%M:%S") - return date_obj.strftime("%d %b %Y, %H:%M") - except: - return str(date_string) - - def calculate_age(birthday_string): - """Calculate age from birthday""" - if not birthday_string: - return "N/A" - - try: - birth_date = datetime.strptime(str(birthday_string), "%Y-%m-%d").date() - today = datetime.now().date() - age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day)) - return age - except: - return "N/A" - - - @_bot.message_handler(commands=["test"]) - def test_handler(message): - markup = types.ReplyKeyboardMarkup(row_width=2, one_time_keyboard=True) - btn1 = types.KeyboardButton("Option 1") - btn2 = types.KeyboardButton("Option 2") - markup.add(btn1, btn2) - _bot.reply_to(message, "Test command received!", reply_markup=markup) - - @_bot.message_handler(func=lambda message: message.text in ["Option 1", "Option 2"]) - def handle_test_options(message): - option = message.text - _bot.send_message(message.chat.id, f"You selected: {option}") - - @_bot.message_handler(func=lambda message: True) - def handle_student_selection(message): - chat_id = message.chat.id - selected_text = message.text - - _bot.reply_to(message, f"You said: {message.text}") - - return _bot \ No newline at end of file diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..cebc5fd --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,18 @@ +import os +import telebot +import logging +from bot.handlers.init import register_all_handlers + +logger = logging.getLogger(__name__) + +_bot = None + + +def get_bot(): + global _bot + if _bot is None: + BOT_TOKEN = os.environ.get("BOT_TOKEN") + _bot = telebot.TeleBot(BOT_TOKEN, threaded=False) + register_all_handlers(_bot) + logger.info("Telegram bot initialized.") + return _bot diff --git a/bot/callbacks/init.py b/bot/callbacks/init.py new file mode 100644 index 0000000..217b108 --- /dev/null +++ b/bot/callbacks/init.py @@ -0,0 +1,7 @@ +from . import students, teachers + + +def register_all_callbacks(bot): + """Register all callback handlers""" + students.register(bot) + teachers.register(bot) diff --git a/bot/callbacks/students.py b/bot/callbacks/students.py new file mode 100644 index 0000000..e7006cd --- /dev/null +++ b/bot/callbacks/students.py @@ -0,0 +1,20 @@ +from telebot import types +from database.models import Students +from bot.utils.formatters import format_student_info + + +def register(bot): + @bot.callback_query_handler(func=lambda call: call.data.startswith('student_')) + def show_student_details(call): + student_id = call.data.split('_')[1] + student = Students.getStudentById(student_id) + + if student: + details = format_student_info(student) + markup = types.InlineKeyboardMarkup() + bot.send_message(call.message.chat.id, details, + reply_markup=markup, parse_mode="HTML") + else: + bot.send_message(call.message.chat.id, "Student not found.") + + bot.answer_callback_query(call.id) diff --git a/bot/callbacks/teachers.py b/bot/callbacks/teachers.py new file mode 100644 index 0000000..b849ed5 --- /dev/null +++ b/bot/callbacks/teachers.py @@ -0,0 +1,20 @@ +from telebot import types +from database.models import Teachers +from bot.utils.formatters import format_teacher_info + + +def register(bot): + @bot.callback_query_handler(func=lambda call: call.data.startswith('teacher_')) + def show_teacher_details(call): + teacher_id = call.data.split('_')[1] + teacher = Teachers.getTeachertById(teacher_id) + + if teacher: + details = format_teacher_info(teacher) + markup = types.InlineKeyboardMarkup() + bot.send_message(call.message.chat.id, details, + reply_markup=markup, parse_mode="HTML") + else: + bot.send_message(call.message.chat.id, "Teacher not found.") + + bot.answer_callback_query(call.id) diff --git a/bot/handlers/common.py b/bot/handlers/common.py new file mode 100644 index 0000000..0071466 --- /dev/null +++ b/bot/handlers/common.py @@ -0,0 +1,4 @@ +def register(bot): + @bot.message_handler(func=lambda message: True) + def handle_unknown(message): + bot.reply_to(message, f"You said: {message.text}") diff --git a/bot/handlers/init.py b/bot/handlers/init.py new file mode 100644 index 0000000..1085dad --- /dev/null +++ b/bot/handlers/init.py @@ -0,0 +1,14 @@ +from . import start, students, teachers, common +from bot.callbacks.init import register_all_callbacks + + +def register_all_handlers(bot): + """Register all handlers and callbacks""" + # Message handlers + start.register(bot) + students.register(bot) + teachers.register(bot) + common.register(bot) # Must be last (catch-all) + + # Callback handlers + register_all_callbacks(bot) diff --git a/bot/handlers/start.py b/bot/handlers/start.py new file mode 100644 index 0000000..90ece6b --- /dev/null +++ b/bot/handlers/start.py @@ -0,0 +1,29 @@ +from telebot import types +from bot.utils.auth import is_authenticated + + +def register(bot): + @bot.message_handler(commands=["start"]) + def start_handler(message): + if is_authenticated(message.from_user.id): + bot.send_message( + message.chat.id, "authorized user! Welcome.") + else: + bot.send_message( + message.chat.id, "Welcome! Use buttons to fetch from database.") + + markup = types.ReplyKeyboardMarkup( + row_width=2, one_time_keyboard=True, resize_keyboard=True) + btn1 = types.KeyboardButton("Show Students") + btn2 = types.KeyboardButton("Show Teachers") + btn3 = types.KeyboardButton("Show Courses") + btn4 = types.KeyboardButton("Show Tag") + btn5 = types.KeyboardButton("Show Categories") + markup.add(btn1, btn2, btn3, btn4, btn5) + bot.reply_to(message, "Use buttons to fetch from database.", + reply_markup=markup) + + @bot.message_handler(func=lambda message: message.text in ["Show Courses", "Show Tag", "Show Categories"]) + def handle_test_options(message): + option = message.text + bot.send_message(message.chat.id, f"You selected: {option}") diff --git a/bot/handlers/students.py b/bot/handlers/students.py new file mode 100644 index 0000000..1d0c98d --- /dev/null +++ b/bot/handlers/students.py @@ -0,0 +1,26 @@ +import logging +from telebot import types +from database.models import Students + +logger = logging.getLogger(__name__) + + +def register(bot): + @bot.message_handler(func=lambda message: message.text == "Show Students") + def get_students(message): + try: + data = Students.getAllStudents() + if data: + markup = types.InlineKeyboardMarkup(row_width=2) + + for row in data: + btn = types.InlineKeyboardButton( + f"@{row[1]} | id#{row[0]}", callback_data=f"student_{row[0]}") + markup.add(btn) + bot.send_message( + message.chat.id, "Here is the data:", reply_markup=markup) + else: + bot.reply_to(message, "No data found.") + except Exception as e: + logger.error(f"Error in get_students handler: {e}") + bot.reply_to(message, "Sorry, an error occurred.") diff --git a/bot/handlers/teachers.py b/bot/handlers/teachers.py new file mode 100644 index 0000000..109751f --- /dev/null +++ b/bot/handlers/teachers.py @@ -0,0 +1,26 @@ +import logging +from telebot import types +from database.models import Teachers + +logger = logging.getLogger(__name__) + + +def register(bot): + @bot.message_handler(func=lambda message: message.text == "Show Teachers") + def get_teachers(message): + try: + data = Teachers.getAllTeachers() + if data: + + markup = types.InlineKeyboardMarkup(row_width=2) + for row in data: + btn = types.InlineKeyboardButton( + f"@{row[1]} | id#{row[0]}", callback_data=f"teacher_{row[0]}") + markup.add(btn) + bot.send_message( + message.chat.id, "Here is the data:", reply_markup=markup) + else: + bot.reply_to(message, "No data found.") + except Exception as e: + logger.error(f"Error in get_teachers handler: {e}") + bot.reply_to(message, "Sorry, an error occurred.") diff --git a/bot/utils/auth.py b/bot/utils/auth.py new file mode 100644 index 0000000..38165f1 --- /dev/null +++ b/bot/utils/auth.py @@ -0,0 +1,4 @@ +authenticated_users = {2032936226} + +def is_authenticated(user_id): + return user_id in authenticated_users \ No newline at end of file diff --git a/bot/utils/formatters.py b/bot/utils/formatters.py new file mode 100644 index 0000000..b9ac134 --- /dev/null +++ b/bot/utils/formatters.py @@ -0,0 +1,109 @@ +from datetime import datetime + + +def format_date(date_string): + """Convert database date to readable format""" + if not date_string: + return "N/A" + + try: + # If date_string is already a datetime object + if isinstance(date_string, datetime): + return date_string.strftime("%d %b %Y, %H:%M") + + # If it's a string, parse it first + date_obj = datetime.strptime(str(date_string), "%Y-%m-%d %H:%M:%S") + return date_obj.strftime("%d %b %Y, %H:%M") + except: + return str(date_string) + + +def calculate_age(birthday_string): + """Calculate age from birthday""" + if not birthday_string: + return "N/A" + + try: + birth_date = datetime.strptime(str(birthday_string), "%Y-%m-%d").date() + today = datetime.now().date() + age = today.year - birth_date.year - \ + ((today.month, today.day) < (birth_date.month, birth_date.day)) + return age + except: + return "N/A" + +# Student Info Formatter + + +def format_student_info(student): + """ + Format student data into a nice message + student is a tuple: (id, username, name, created_at, email, phone_number, last_seen, is_verified, birthday) + """ + print(student) + id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday = student + + verified_status = "✅ Verified" if is_verfied else "❌ Not Verified" + + # Format dates nicely + created_date = format_date(created_at) + last_seen_date = format_date(last_seen) + + # Calculate age from birthday + age = calculate_age(birthday) + + details = f""" + 👤 Student Profile + + Name: {name} + Username: {username} + Email: {email} + Phone: {phone_number} + Birthday: {birthday} (Age: {age}) + Account Status: {verified_status} + + 📅 Account Info + Joined: {created_date} + Last Seen: {last_seen_date} + ID: {id} + """ + return details.strip() + + +# Teacher Info Formatter +def format_teacher_info(teacher): + """ + Format student data into a nice message + student is a tuple: (id, username, created_at, email, phone_number, last_seen, is_verified, birthday) + """ + print(teacher) + id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday, about_me, job_title = teacher + + verified_status = "✅ Verified" if is_verfied else "❌ Not Verified" + + # Format dates nicely + created_date = format_date(created_at) + last_seen_date = format_date(last_seen) + + # Calculate age from birthday + age = calculate_age(birthday) + + details = f""" + 👤 Student Profile + + Name: {name} + Username: {username} + Job Title: {job_title} + About Me: {about_me} + + Email: {email} + Phone: {phone_number} + Birthday: {birthday} (Age: {age}) + Account Status: {verified_status} + + 📅 Account Info + Joined: {created_date} + Last Seen: {last_seen_date} + ID: {id} + """ + return details.strip() diff --git a/database/models.py b/database/models.py index 21be51d..88d7fa9 100644 --- a/database/models.py +++ b/database/models.py @@ -11,9 +11,9 @@ class Students: conn = connection.get_db_connection() if not conn: return None - + cursor = conn.cursor() - cursor.execute("SELECT id,name FROM students"); + cursor.execute("SELECT id,username FROM students") result = cursor.fetchall() cursor.close() @@ -31,9 +31,10 @@ class Students: conn = connection.get_db_connection() if not conn: return None - + cursor = conn.cursor() - cursor.execute("SELECT id,username, created_at, email, phone_number, last_seen, is_verfied, birthday FROM students WHERE id = %s", (student_id,)) + cursor.execute( + "SELECT id,username, name, created_at, email, phone_number, last_seen, is_verfied, birthday FROM students WHERE id = %s", (student_id,)) result = cursor.fetchall() cursor.close() @@ -43,4 +44,47 @@ class Students: return None finally: if conn: - connection.release_db_connection(conn) \ No newline at end of file + connection.release_db_connection(conn) + + +class Teachers: + def getAllTeachers(): + conn = None + try: + conn = connection.get_db_connection() + if not conn: + return None + + cursor = conn.cursor() + cursor.execute("SELECT id,username FROM teachers") + result = cursor.fetchall() + cursor.close() + + return result if result else None + except Exception as e: + logger.error(f"Database query error in getAllTeachers: {e}") + return None + finally: + if conn: + connection.release_db_connection(conn) + + def getTeachertById(teacher_id): + conn = None + try: + conn = connection.get_db_connection() + if not conn: + return None + + cursor = conn.cursor() + cursor.execute( + "SELECT id,username, name, created_at, email, phone_number, last_seen, is_verfied, birthday, about_me, job_title FROM teachers WHERE id = %s", (teacher_id)) + result = cursor.fetchall() + cursor.close() + + return result[0] if result else None + except Exception as e: + logger.error(f"Database query error in getTeacherById: {e}") + return None + finally: + if conn: + connection.release_db_connection(conn) diff --git a/dev.py b/dev.py index ff0e83a..4288e74 100644 --- a/dev.py +++ b/dev.py @@ -1,6 +1,6 @@ import os import requests -from bot import get_bot +from bot.bot import get_bot from dotenv import load_dotenv load_dotenv() diff --git a/main.py b/main.py index 9984f32..235ac42 100644 --- a/main.py +++ b/main.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) async def telegram_webhook(request: Request): try: import telebot - from bot import get_bot + from bot.bot import get_bot bot = get_bot() json_data = await request.json()