Initial commit

This commit is contained in:
root
2025-09-15 00:47:01 +02:00
commit a50fa92542
8 changed files with 1200 additions and 0 deletions

373
handlers/admin_handlers.py Normal file
View File

@@ -0,0 +1,373 @@
# handlers/admin_handlers.py
import sqlite3
import asyncio
from aiogram import Router, F, types, Bot
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.enums import ContentType
from aiogram.exceptions import TelegramAPIError
from keyboards import (
get_main_keyboard_by_role, get_users_keyboard, user_management_keyboard,
get_users_for_configs_keyboard, get_user_configs_management_keyboard,
get_tutorials_admin_keyboard, get_skip_media_keyboard, get_confirm_send_keyboard
)
from localization import get_text
router = Router()
# --- Helper function to get admin's language ---
def get_admin_lang(user_id: int) -> str:
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT language_code FROM users WHERE telegram_id = ?", (user_id,))
result = cursor.fetchone()
conn.close()
return result[0] if result else 'en'
# --- FSM States ---
class AdminStates(StatesGroup):
add_user_id = State()
add_config_type = State()
add_config_data = State()
add_tutorial_title = State()
add_tutorial_text = State()
add_tutorial_media = State()
mass_send_message = State()
mass_send_confirm = State()
# --- Main Menu Handler ---
@router.callback_query(F.data == "admin_menu")
async def process_admin_menu(callback: types.CallbackQuery, state: FSMContext):
await state.clear() # Clear any active state
lang = get_admin_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('welcome_admin', lang),
reply_markup=get_main_keyboard_by_role(is_admin=True, lang=lang)
)
await callback.answer()
# --- User Management Section ---
@router.callback_query(F.data.startswith("admin_users_page_"))
async def process_users_list(callback: types.CallbackQuery):
page = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('users_list', lang),
reply_markup=get_users_keyboard(page, lang)
)
await callback.answer()
@router.callback_query(F.data.startswith("manage_user_"))
async def process_manage_user(callback: types.CallbackQuery):
user_id = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT username FROM users WHERE telegram_id = ?", (user_id,))
user = cursor.fetchone()
conn.close()
username = user[0] if user and user[0] else "N/A"
text = get_text('manage_user_title', lang).format(user_id=user_id, username=username)
await callback.message.edit_text(
text,
reply_markup=user_management_keyboard(user_id, lang),
parse_mode="Markdown"
)
await callback.answer()
@router.callback_query(F.data.startswith("delete_user_"))
async def process_delete_user(callback: types.CallbackQuery):
user_id = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("DELETE FROM users WHERE telegram_id = ?", (user_id,))
# Configs will be deleted automatically due to "ON DELETE CASCADE" in the new DB schema
conn.commit()
conn.close()
await callback.answer(get_text('user_deleted_ok', lang))
await callback.message.edit_text(
get_text('users_list', lang),
reply_markup=get_users_keyboard(0, lang)
)
@router.callback_query(F.data == "add_user")
async def process_add_user_start(callback: types.CallbackQuery, state: FSMContext):
lang = get_admin_lang(callback.from_user.id)
await callback.message.edit_text(get_text('ask_for_user_id', lang))
await state.set_state(AdminStates.add_user_id)
await callback.answer()
@router.message(AdminStates.add_user_id)
async def process_add_user_id(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
try:
user_id = int(message.text)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT telegram_id FROM users WHERE telegram_id = ?", (user_id,))
if cursor.fetchone():
await message.answer(get_text('user_already_exists', lang))
else:
# Add user with default language 'en'
cursor.execute("INSERT INTO users (telegram_id, language_code) VALUES (?, ?)", (user_id, 'en'))
conn.commit()
await message.answer(get_text('user_added_ok', lang))
conn.close()
await state.clear()
await message.answer(
get_text('users_list', lang),
reply_markup=get_users_keyboard(0, lang)
)
except (ValueError, TypeError):
await message.answer(get_text('invalid_id_format', lang))
# --- Config Management Section ---
# (This section is also refactored to use the lang parameter)
@router.callback_query(F.data.startswith("admin_configs_page_"))
async def process_config_users_list(callback: types.CallbackQuery):
page = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('choose_user_for_config', lang),
reply_markup=get_users_for_configs_keyboard(page, lang)
)
await callback.answer()
@router.callback_query(F.data.startswith("user_configs_manage_"))
async def process_user_configs_manage(callback: types.CallbackQuery):
user_id = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('user_configs_title', lang),
reply_markup=get_user_configs_management_keyboard(user_id, lang)
)
await callback.answer()
@router.callback_query(F.data.startswith("delete_config:"))
async def process_delete_config(callback: types.CallbackQuery):
_, config_id, user_id = callback.data.split(":")
config_id, user_id = int(config_id), int(user_id)
lang = get_admin_lang(callback.from_user.id)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("DELETE FROM configs WHERE id = ?", (config_id,))
conn.commit()
conn.close()
await callback.answer(get_text('config_deleted_ok', lang))
await callback.message.edit_text(
get_text('user_configs_title', lang),
reply_markup=get_user_configs_management_keyboard(user_id, lang)
)
# ... (Add Config FSM flow refactored for localization)
@router.callback_query(F.data.startswith("add_config_"))
async def process_add_config_start(callback: types.CallbackQuery, state: FSMContext):
user_id = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
await state.update_data(current_user_id=user_id)
await state.set_state(AdminStates.add_config_type)
await callback.message.edit_text(get_text('add_config_step1', lang))
await callback.answer()
@router.message(AdminStates.add_config_type)
async def process_add_config_type(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
await state.update_data(config_type=message.text)
await state.set_state(AdminStates.add_config_data)
await message.answer(get_text('add_config_step2', lang))
@router.message(AdminStates.add_config_data, F.content_type.in_({ContentType.TEXT, ContentType.DOCUMENT}))
async def process_add_config_data(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
data = await state.get_data()
user_id = data['current_user_id']
# ... (logic for handling file/text remains the same)
if message.document:
file_id = message.document.file_id
file_name = message.document.file_name
config_type = f"file:{file_name}"
config_data = file_id
else:
config_type = data['config_type']
config_data = message.text
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO configs (user_id, config_type, config_data) VALUES (?, ?, ?)",
(user_id, config_type, config_data))
conn.commit()
conn.close()
await state.clear()
await message.answer(get_text('config_added_ok', lang))
await message.answer(
get_text('user_configs_title', lang),
reply_markup=get_user_configs_management_keyboard(user_id, lang)
)
# --- Tutorial Management Section ---
@router.callback_query(F.data == "admin_tutorials_menu")
async def process_tutorials_menu(callback: types.CallbackQuery):
lang = get_admin_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('tutorials_menu_title', lang),
reply_markup=get_tutorials_admin_keyboard(lang)
)
await callback.answer()
# ... (rest of the tutorial management refactored similarly)
@router.callback_query(F.data.startswith("delete_tutorial_"))
async def process_delete_tutorial(callback: types.CallbackQuery):
tutorial_id = int(callback.data.split("_")[-1])
lang = get_admin_lang(callback.from_user.id)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("DELETE FROM tutorials WHERE id = ?", (tutorial_id,))
conn.commit()
conn.close()
await callback.answer(get_text('tutorial_deleted_ok', lang))
await callback.message.edit_text(
get_text('tutorials_menu_title', lang),
reply_markup=get_tutorials_admin_keyboard(lang)
)
@router.callback_query(F.data == "add_tutorial")
async def process_add_tutorial_start(callback: types.CallbackQuery, state: FSMContext):
lang = get_admin_lang(callback.from_user.id)
await state.set_state(AdminStates.add_tutorial_title)
await callback.message.edit_text(get_text('add_tutorial_step1', lang))
await callback.answer()
@router.message(AdminStates.add_tutorial_title)
async def process_add_tutorial_title(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
await state.update_data(title=message.text)
await state.set_state(AdminStates.add_tutorial_text)
await message.answer(get_text('add_tutorial_step2', lang))
@router.message(AdminStates.add_tutorial_text)
async def process_add_tutorial_text(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
await state.update_data(text=message.text)
await state.set_state(AdminStates.add_tutorial_media)
await message.answer(
get_text('add_tutorial_step3', lang),
reply_markup=get_skip_media_keyboard(lang)
)
@router.callback_query(F.data == "skip_media", AdminStates.add_tutorial_media)
async def process_skip_media(callback: types.CallbackQuery, state: FSMContext):
lang = get_admin_lang(callback.from_user.id)
data = await state.get_data()
# ... (DB logic is the same)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO tutorials (title, content_text) VALUES (?, ?)", (data['title'], data['text']))
conn.commit()
conn.close()
await state.clear()
await callback.message.edit_text(get_text('tutorial_added_ok_no_media', lang))
await callback.message.answer(
get_text('tutorials_menu_title', lang),
reply_markup=get_tutorials_admin_keyboard(lang)
)
await callback.answer()
@router.message(AdminStates.add_tutorial_media, F.content_type.in_({ContentType.PHOTO, ContentType.VIDEO}))
async def process_add_tutorial_media(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
# ... (DB and file_id logic is the same)
file_id = ""
if message.photo:
file_id = message.photo[-1].file_id
elif message.video:
file_id = message.video.file_id
data = await state.get_data()
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO tutorials (title, content_text, file_id) VALUES (?, ?, ?)",
(data['title'], data['text'], file_id))
conn.commit()
conn.close()
await state.clear()
await message.answer(get_text('tutorial_added_ok_with_media', lang))
await message.answer(
get_text('tutorials_menu_title', lang),
reply_markup=get_tutorials_admin_keyboard(lang)
)
# --- Mass Messaging Section ---
@router.callback_query(F.data == "mass_send_start")
async def process_mass_send_start(callback: types.CallbackQuery, state: FSMContext):
lang = get_admin_lang(callback.from_user.id)
await state.set_state(AdminStates.mass_send_message)
await callback.message.edit_text(get_text('mass_send_ask_message', lang))
await callback.answer()
@router.message(AdminStates.mass_send_message)
async def process_mass_send_message(message: types.Message, state: FSMContext):
lang = get_admin_lang(message.from_user.id)
await state.update_data(message_to_send=message)
await state.set_state(AdminStates.mass_send_confirm)
await message.answer(
get_text('mass_send_confirm_message', lang),
reply_markup=get_confirm_send_keyboard(lang)
)
@router.callback_query(F.data == "send_cancelled", AdminStates.mass_send_confirm)
async def process_send_cancelled(callback: types.CallbackQuery, state: FSMContext):
lang = get_admin_lang(callback.from_user.id)
await state.clear()
await callback.message.edit_text(
get_text('mass_send_cancelled', lang),
reply_markup=get_main_keyboard_by_role(is_admin=True, lang=lang)
)
await callback.answer()
@router.callback_query(F.data == "send_confirmed", AdminStates.mass_send_confirm)
async def process_send_confirmed(callback: types.CallbackQuery, state: FSMContext, bot: Bot):
lang = get_admin_lang(callback.from_user.id)
data = await state.get_data()
message_to_send = data['message_to_send']
await state.clear()
await callback.message.edit_text(get_text('mass_send_started', lang), reply_markup=None)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT telegram_id FROM users WHERE is_admin = 0")
user_ids = cursor.fetchall()
conn.close()
success_count = 0
fail_count = 0
for (user_id,) in user_ids:
try:
await message_to_send.copy_to(chat_id=user_id)
success_count += 1
except TelegramAPIError as e:
print(f"Failed to send to {user_id}: {e}")
fail_count += 1
await asyncio.sleep(0.1)
result_text = get_text('mass_send_finished', lang).format(
success_count=success_count,
fail_count=fail_count
)
await callback.message.answer(
result_text,
reply_markup=get_main_keyboard_by_role(is_admin=True, lang=lang)
)

View File

@@ -0,0 +1,48 @@
# handlers/settings_handlers.py
import sqlite3
from aiogram import Router, F, types
from keyboards import get_language_choice_keyboard, get_main_keyboard_by_role
from localization import get_text
router = Router()
@router.callback_query(F.data == "settings")
async def process_settings(callback: types.CallbackQuery):
# Получаем язык пользователя для корректного отображения меню
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT language_code FROM users WHERE telegram_id = ?", (callback.from_user.id,))
lang = cursor.fetchone()[0]
conn.close()
await callback.message.edit_text(
get_text('choose_language', lang),
reply_markup=get_language_choice_keyboard()
)
await callback.answer()
@router.callback_query(F.data.startswith("set_lang_"))
async def process_set_language(callback: types.CallbackQuery):
lang_code = callback.data.split("_")[-1]
user_id = callback.from_user.id
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("UPDATE users SET language_code = ? WHERE telegram_id = ?", (lang_code, user_id))
cursor.execute("SELECT is_admin FROM users WHERE telegram_id = ?", (user_id,))
is_admin = cursor.fetchone()[0]
conn.commit()
conn.close()
await callback.message.edit_text(get_text('language_changed', lang_code))
# Показываем главное меню на новом языке
main_menu_text = get_text('welcome_admin' if is_admin else 'welcome', lang_code)
await callback.message.answer(
main_menu_text,
reply_markup=get_main_keyboard_by_role(is_admin, lang_code)
)
await callback.answer()

98
handlers/user_handlers.py Normal file
View File

@@ -0,0 +1,98 @@
# handlers/user_handlers.py
import sqlite3
from aiogram import Router, F, types, Bot
from keyboards import get_main_keyboard_by_role, get_tutorials_user_keyboard
from localization import get_text
router = Router()
# Вспомогательная функция для получения языка пользователя
def get_user_lang(user_id: int) -> str:
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT language_code FROM users WHERE telegram_id = ?", (user_id,))
result = cursor.fetchone()
conn.close()
return result[0] if result else 'en'
# Новый обработчик для кнопки "Назад в меню" из раздела помощи
@router.callback_query(F.data == "user_main_menu")
async def process_back_to_main_menu(callback: types.CallbackQuery):
lang = get_user_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('welcome', lang),
reply_markup=get_main_keyboard_by_role(is_admin=False, lang=lang)
)
await callback.answer()
@router.callback_query(F.data == "user_configs")
async def process_user_configs(callback: types.CallbackQuery, bot: Bot):
user_id = callback.from_user.id
lang = get_user_lang(user_id)
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT config_type, config_data FROM configs WHERE user_id = ?", (user_id,))
user_configs = cursor.fetchall()
conn.close()
if not user_configs:
await callback.message.answer(get_text('no_configs_yet', lang))
else:
await callback.message.answer(get_text('your_configs', lang))
for config_type, config_data in user_configs:
if config_type.startswith("file:"):
file_id = config_data
caption = f"{get_text('config_type', lang)}: {config_type.split(':', 1)[1]}"
await bot.send_document(chat_id=user_id, document=file_id, caption=caption)
else:
await callback.message.answer(f"{get_text('config_type', lang)}: `{config_type}`\n\n`{config_data}`", parse_mode="Markdown")
await callback.message.answer(
get_text('next_action', lang),
reply_markup=get_main_keyboard_by_role(is_admin=False, lang=lang)
)
await callback.answer()
@router.callback_query(F.data == "user_help")
async def process_user_help(callback: types.CallbackQuery):
lang = get_user_lang(callback.from_user.id)
await callback.message.edit_text(
get_text('choose_tutorial', lang),
reply_markup=get_tutorials_user_keyboard(lang)
)
await callback.answer()
@router.callback_query(F.data.startswith("view_tutorial_"))
async def process_view_tutorial(callback: types.CallbackQuery, bot: Bot):
user_id = callback.from_user.id
lang = get_user_lang(user_id)
tutorial_id = int(callback.data.split("_")[-1])
conn = sqlite3.connect('bot.db')
cursor = conn.cursor()
cursor.execute("SELECT content_text, file_id FROM tutorials WHERE id = ?", (tutorial_id,))
tutorial = cursor.fetchone()
conn.close()
if tutorial:
content_text, file_id = tutorial
# Сначала удаляем предыдущее сообщение с кнопками
await callback.message.delete()
if file_id:
try:
await bot.send_photo(chat_id=user_id, photo=file_id, caption=content_text)
except:
await bot.send_video(chat_id=user_id, video=file_id, caption=content_text)
else:
await callback.message.answer(content_text)
await callback.message.answer(
get_text('next_action', lang),
reply_markup=get_main_keyboard_by_role(is_admin=False, lang=lang)
)
else:
await callback.answer(get_text('error_not_found', lang), show_alert=True)
await callback.answer()