From 6cb49be951ee93f39ecb3a33d614a56d577a1a42 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 27 Nov 2025 18:58:17 +0100 Subject: [PATCH] chore: update gitignore, add translations --- .gitignore | 31 ++- edu_master/webinar-checker/checker.py | 277 +++++++++++++++++++++----- userbot/.gitignore | 11 +- 3 files changed, 254 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index f29520c..f36e287 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,35 @@ traefik/certs/* # Python .python-version -.venv/ venv/ +pyc +unknown_errors.txt +moonlogs.txt +thumb.jpg +antipm_pic.jpg +musicbot/ +.trunk/ +previous_profiles/ +.python-version +/modules/__pycache__/ +__pycache__/ +*.session +*.session-old +*.db +*.sqlite3 +*-journal +/venv/ +.venv/ -#DataSecurity +# DataSecurity replacements.txt + +# Vscode +.vscode + +# Git +.gitattributes + +# Misc +.DS_Store +.idea \ No newline at end of file diff --git a/edu_master/webinar-checker/checker.py b/edu_master/webinar-checker/checker.py index 6eb5f50..0477481 100644 --- a/edu_master/webinar-checker/checker.py +++ b/edu_master/webinar-checker/checker.py @@ -40,6 +40,156 @@ except Exception as e: logger.error(f"Failed to connect to Redis: {e}") exit(1) +# --- Translations --- + +TRANSLATIONS = { + 'ru': { + 'welcome': "👋 Привет, {name}!\n\nЯ бот-уведомитель о вебинарах. Я буду сообщать вам, когда появится новый вебинар.\nВы подписаны на уведомления.", + 'welcome_admin': "\n\n👑 Режим администратора активен", + 'access_denied': "⛔ Доступ запрещен. Вас нет в белом списке.", + 'help_title': "🤖 Помощь по боту\n\n", + 'help_commands': "/start - Подписаться на уведомления\n/help - Показать это сообщение\n/language - Сменить язык", + 'help_admin': "\nКоманды администратора:\n/adduser [user_id] - Добавить пользователя в белый список\n/removeuser [user_id] - Удалить пользователя из белого списка\nИли используйте панель ниже для управления настройками.", + 'admin_only': "⛔ Только для администратора!", + 'user_added': "✅ Пользователь {user_id} добавлен в белый список", + 'user_removed': "✅ Пользователь {user_id} удален из белого списка", + 'user_not_in_whitelist': "⚠️ Пользователь {user_id} не был в белом списке", + 'cannot_remove_admin': "❌ Невозможно удалить администратора из белого списка", + 'invalid_user_id': "❌ Неверный ID пользователя. Должно быть число.", + 'usage_adduser': "Использование: /adduser [user_id]", + 'usage_removeuser': "Использование: /removeuser [user_id]", + 'whitelist_enabled': "✅ Белый список включен", + 'whitelist_disabled': "✅ Белый список отключен", + 'whitelist_title': "📋 Белый список:\n", + 'subscribers_title': "👥 Подписчики:\n", + 'empty': "Пусто", + 'force_check_running': "🔄 Запускаю проверку...", + 'check_failed': "❌ Проверка не удалась. Смотрите логи.", + 'check_completed_none': "✅ Проверка завершена. Вебинаров не найдено.", + 'check_completed': "✅ Проверка завершена. Найдено {count} вебинар(ов)!", + 'toggle_whitelist_disable': "🔒 Отключить белый список", + 'toggle_whitelist_enable': "🔓 Включить белый список", + 'view_whitelist': "📋 Посмотреть белый список", + 'view_subscribers': "👥 Посмотреть подписчиков", + 'force_check': "🔄 Принудительная проверка", + 'webinar_found': "🎓 Новый вебинар!\n\n", + 'webinar_item': "📌 {name}\n🔗 https://edu.edu.vn.ua{url}", + 'select_language': "🌐 Выберите язык / Оберіть мову / Select language:", + 'language_changed': "✅ Язык изменен на русский", + 'flag_ru': "🇷🇺 Русский", + 'flag_uk': "🇺🇦 Українська", + 'flag_en': "🇬🇧 English", + }, + 'uk': { + 'welcome': "👋 Привіт, {name}!\n\nЯ бот-сповіщувач про вебінари. Я повідомлятиму вас, коли з'явиться новий вебінар.\nВи підписані на сповіщення.", + 'welcome_admin': "\n\n👑 Режим адміністратора активний", + 'access_denied': "⛔ Доступ заборонено. Вас немає в білому списку.", + 'help_title': "🤖 Довідка по боту\n\n", + 'help_commands': "/start - Підписатися на сповіщення\n/help - Показати це повідомлення\n/language - Змінити мову", + 'help_admin': "\nКоманди адміністратора:\n/adduser [user_id] - Додати користувача до білого списку\n/removeuser [user_id] - Видалити користувача з білого списку\nАбо використовуйте панель нижче для керування налаштуваннями.", + 'admin_only': "⛔ Тільки для адміністратора!", + 'user_added': "✅ Користувач {user_id} доданий до білого списку", + 'user_removed': "✅ Користувач {user_id} видалений з білого списку", + 'user_not_in_whitelist': "⚠️ Користувач {user_id} не був у білому списку", + 'cannot_remove_admin': "❌ Неможливо видалити адміністратора з білого списку", + 'invalid_user_id': "❌ Невірний ID користувача. Має бути число.", + 'usage_adduser': "Використання: /adduser [user_id]", + 'usage_removeuser': "Використання: /removeuser [user_id]", + 'whitelist_enabled': "✅ Білий список увімкнено", + 'whitelist_disabled': "✅ Білий список вимкнено", + 'whitelist_title': "📋 Білий список:\n", + 'subscribers_title': "👥 Підписники:\n", + 'empty': "Порожньо", + 'force_check_running': "🔄 Запускаю перевірку...", + 'check_failed': "❌ Перевірка не вдалася. Дивіться логи.", + 'check_completed_none': "✅ Перевірка завершена. Вебінарів не знайдено.", + 'check_completed': "✅ Перевірка завершена. Знайдено {count} вебінар(ів)!", + 'toggle_whitelist_disable': "🔒 Вимкнути білий список", + 'toggle_whitelist_enable': "🔓 Увімкнути білий список", + 'view_whitelist': "📋 Переглянути білий список", + 'view_subscribers': "👥 Переглянути підписників", + 'force_check': "🔄 Примусова перевірка", + 'webinar_found': "🎓 Новий вебінар!\n\n", + 'webinar_item': "📌 {name}\n🔗 https://edu.edu.vn.ua{url}", + 'select_language': "🌐 Виберіть мову / Выберите язык / Select language:", + 'language_changed': "✅ Мову змінено на українську", + 'flag_ru': "🇷🇺 Русский", + 'flag_uk': "🇺🇦 Українська", + 'flag_en': "🇬🇧 English", + }, + 'en': { + 'welcome': "👋 Hello, {name}!\n\nI am the Webinar Checker Bot. I will notify you when a new webinar appears.\nYou have been subscribed to notifications.", + 'welcome_admin': "\n\n👑 Admin Mode Active", + 'access_denied': "⛔ Access denied. You are not on the whitelist.", + 'help_title': "🤖 Bot Help\n\n", + 'help_commands': "/start - Subscribe to notifications\n/help - Show this message\n/language - Change language", + 'help_admin': "\nAdmin Commands:\n/adduser [user_id] - Add user to whitelist\n/removeuser [user_id] - Remove user from whitelist\nOr use the panel below to manage settings.", + 'admin_only': "⛔ Admin only!", + 'user_added': "✅ User {user_id} added to whitelist", + 'user_removed': "✅ User {user_id} removed from whitelist", + 'user_not_in_whitelist': "⚠️ User {user_id} was not in whitelist", + 'cannot_remove_admin': "❌ Cannot remove admin from whitelist", + 'invalid_user_id': "❌ Invalid user ID. Must be a number.", + 'usage_adduser': "Usage: /adduser [user_id]", + 'usage_removeuser': "Usage: /removeuser [user_id]", + 'whitelist_enabled': "✅ Whitelist Enabled", + 'whitelist_disabled': "✅ Whitelist Disabled", + 'whitelist_title': "📋 Whitelist:\n", + 'subscribers_title': "👥 Subscribers:\n", + 'empty': "Empty", + 'force_check_running': "🔄 Running immediate check...", + 'check_failed': "❌ Check failed. See logs for details.", + 'check_completed_none': "✅ Check completed. No webinars found.", + 'check_completed': "✅ Check completed. Found {count} webinar(s)!", + 'toggle_whitelist_disable': "🔒 Disable Whitelist", + 'toggle_whitelist_enable': "🔓 Enable Whitelist", + 'view_whitelist': "📋 View Whitelist", + 'view_subscribers': "👥 View Subscribers", + 'force_check': "🔄 Force Check", + 'webinar_found': "🎓 New webinar found!\n\n", + 'webinar_item': "📌 {name}\n🔗 https://edu.edu.vn.ua{url}", + 'select_language': "🌐 Select language / Виберіть мову / Выберите язык:", + 'language_changed': "✅ Language changed to English", + 'flag_ru': "🇷🇺 Русский", + 'flag_uk': "🇺🇦 Українська", + 'flag_en': "🇬🇧 English", + } +} + +# --- Language Helper Functions --- + +def get_user_language(user_id: int) -> str: + """Get user's preferred language from Redis. Default: Ukrainian.""" + lang = redis_client.get(f"user:{user_id}:language") + return lang if lang in ['ru', 'uk', 'en'] else 'uk' + +def set_user_language(user_id: int, lang: str): + """Save user's language preference to Redis.""" + if lang in ['ru', 'uk', 'en']: + redis_client.set(f"user:{user_id}:language", lang) + logger.info(f"User {user_id} language set to {lang}") + +def t(user_id: int, key: str, **kwargs) -> str: + """Translate message for user with optional formatting.""" + lang = get_user_language(user_id) + message = TRANSLATIONS.get(lang, TRANSLATIONS['uk']).get(key, key) + if kwargs: + return message.format(**kwargs) + return message + +def get_language_keyboard(): + """Generate language selection keyboard.""" + keyboard = [ + [ + InlineKeyboardButton("🇷🇺 Русский", callback_data="lang_ru"), + InlineKeyboardButton("🇺🇦 Українська", callback_data="lang_uk"), + ], + [ + InlineKeyboardButton("🇬🇧 English", callback_data="lang_en"), + ] + ] + return InlineKeyboardMarkup(keyboard) + # --- Helper Functions --- def is_whitelisted(user_id: int) -> bool: @@ -53,16 +203,16 @@ def is_whitelisted(user_id: int) -> bool: return redis_client.sismember(KEY_WHITELIST, str(user_id)) -def get_admin_keyboard(): +def get_admin_keyboard(user_id: int): """Generate admin panel keyboard.""" whitelist_enabled = redis_client.get(KEY_WHITELIST_ENABLED) != "0" - toggle_text = "🔒 Disable Whitelist" if whitelist_enabled else "🔓 Enable Whitelist" + toggle_text = t(user_id, 'toggle_whitelist_disable') if whitelist_enabled else t(user_id, 'toggle_whitelist_enable') keyboard = [ [InlineKeyboardButton(toggle_text, callback_data="toggle_whitelist")], - [InlineKeyboardButton("📋 View Whitelist", callback_data="view_whitelist")], - [InlineKeyboardButton("👥 View Subscribers", callback_data="view_subscribers")], - [InlineKeyboardButton("🔄 Force Check", callback_data="force_check")] + [InlineKeyboardButton(t(user_id, 'view_whitelist'), callback_data="view_whitelist")], + [InlineKeyboardButton(t(user_id, 'view_subscribers'), callback_data="view_subscribers")], + [InlineKeyboardButton(t(user_id, 'force_check'), callback_data="force_check")] ] return InlineKeyboardMarkup(keyboard) @@ -74,84 +224,84 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): logger.info(f"User {user.id} ({user.username}) started the bot.") if not is_whitelisted(user.id): - await update.message.reply_text("⛔ Access denied. You are not on the whitelist.") + await update.message.reply_text(t(user.id, 'access_denied')) return # Add to subscribers redis_client.sadd(KEY_SUBSCRIBERS, user.id) - msg = ( - f"👋 Hello {user.first_name}!\n\n" - "I am the Webinar Checker Bot. I will notify you when a new webinar appears.\n" - "You have been subscribed to notifications." - ) + msg = t(user.id, 'welcome', name=user.first_name) if user.id == ADMIN_ID: - msg += "\n\n👑 Admin Mode Active" - await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard()) + msg += t(user.id, 'welcome_admin') + await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user.id)) else: await update.message.reply_text(msg, parse_mode='HTML') async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /help command.""" - msg = ( - "🤖 Bot Help\n\n" - "/start - Subscribe to notifications\n" - "/help - Show this message\n" - ) - if update.effective_user.id == ADMIN_ID: - msg += ( - "\nAdmin Commands:\n" - "/adduser [user_id] - Add user to whitelist\n" - "/removeuser [user_id] - Remove user from whitelist\n" - "Or use the panel below to manage settings." - ) - await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard()) + user_id = update.effective_user.id + msg = t(user_id, 'help_title') + t(user_id, 'help_commands') + + if user_id == ADMIN_ID: + msg += t(user_id, 'help_admin') + await update.message.reply_text(msg, parse_mode='HTML', reply_markup=get_admin_keyboard(user_id)) else: await update.message.reply_text(msg, parse_mode='HTML') +async def language_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """Handle /language command.""" + user_id = update.effective_user.id + await update.message.reply_text( + t(user_id, 'select_language'), + parse_mode='HTML', + reply_markup=get_language_keyboard() + ) + async def add_user(update: Update, context: ContextTypes.DEFAULT_TYPE): """Add user to whitelist (admin only).""" - if update.effective_user.id != ADMIN_ID: - await update.message.reply_text("⛔ Admin only!") + admin_id = update.effective_user.id + if admin_id != ADMIN_ID: + await update.message.reply_text(t(admin_id, 'admin_only')) return if not context.args: - await update.message.reply_text("Usage: /adduser [user_id]") + await update.message.reply_text(t(admin_id, 'usage_adduser')) return try: user_id = int(context.args[0]) redis_client.sadd(KEY_WHITELIST, str(user_id)) - await update.message.reply_text(f"✅ User {user_id} added to whitelist") + await update.message.reply_text(t(admin_id, 'user_added', user_id=user_id)) logger.info(f"Admin added user {user_id} to whitelist") except ValueError: - await update.message.reply_text("❌ Invalid user ID. Must be a number.") + await update.message.reply_text(t(admin_id, 'invalid_user_id')) async def remove_user(update: Update, context: ContextTypes.DEFAULT_TYPE): """Remove user from whitelist (admin only).""" - if update.effective_user.id != ADMIN_ID: - await update.message.reply_text("⛔ Admin only!") + admin_id = update.effective_user.id + if admin_id != ADMIN_ID: + await update.message.reply_text(t(admin_id, 'admin_only')) return if not context.args: - await update.message.reply_text("Usage: /removeuser [user_id]") + await update.message.reply_text(t(admin_id, 'usage_removeuser')) return try: user_id = int(context.args[0]) if str(user_id) == str(ADMIN_ID): - await update.message.reply_text("❌ Cannot remove admin from whitelist") + await update.message.reply_text(t(admin_id, 'cannot_remove_admin')) return removed = redis_client.srem(KEY_WHITELIST, str(user_id)) if removed: - await update.message.reply_text(f"✅ User {user_id} removed from whitelist") + await update.message.reply_text(t(admin_id, 'user_removed', user_id=user_id)) logger.info(f"Admin removed user {user_id} from whitelist") else: - await update.message.reply_text(f"⚠️ User {user_id} was not in whitelist") + await update.message.reply_text(t(admin_id, 'user_not_in_whitelist', user_id=user_id)) except ValueError: - await update.message.reply_text("❌ Invalid user ID. Must be a number.") + await update.message.reply_text(t(admin_id, 'invalid_user_id')) # --- Admin Callbacks --- @@ -161,7 +311,7 @@ async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = query.from_user.id if user_id != ADMIN_ID: - await query.answer("⛔ Admin only!", show_alert=True) + await query.answer(t(user_id, 'admin_only'), show_alert=True) return await query.answer() @@ -171,30 +321,45 @@ async def admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): current = redis_client.get(KEY_WHITELIST_ENABLED) new_state = "0" if current != "0" else "1" redis_client.set(KEY_WHITELIST_ENABLED, new_state) - state_text = "Disabled" if new_state == "0" else "Enabled" - await query.edit_message_reply_markup(reply_markup=get_admin_keyboard()) - await query.message.reply_text(f"✅ Whitelist {state_text}") + state_text = t(user_id, 'whitelist_disabled') if new_state == "0" else t(user_id, 'whitelist_enabled') + await query.edit_message_reply_markup(reply_markup=get_admin_keyboard(user_id)) + await query.message.reply_text(state_text) elif data == "view_whitelist": members = redis_client.smembers(KEY_WHITELIST) - msg = "📋 Whitelist:\n" + ("\n".join(members) if members else "Empty") + msg = t(user_id, 'whitelist_title') + ("\n".join(members) if members else t(user_id, 'empty')) await query.message.reply_text(msg, parse_mode='HTML') elif data == "view_subscribers": subs = redis_client.smembers(KEY_SUBSCRIBERS) - msg = "👥 Subscribers:\n" + ("\n".join(subs) if subs else "Empty") + msg = t(user_id, 'subscribers_title') + ("\n".join(subs) if subs else t(user_id, 'empty')) await query.message.reply_text(msg, parse_mode='HTML') elif data == "force_check": - await query.message.reply_text("🔄 Running immediate check...") + await query.message.reply_text(t(user_id, 'force_check_running')) result = await check_webinars_job(context) if result is None: - await query.message.reply_text("❌ Check failed. See logs for details.") + await query.message.reply_text(t(user_id, 'check_failed')) elif result == 0: - await query.message.reply_text("✅ Check completed. No webinars found.") + await query.message.reply_text(t(user_id, 'check_completed_none')) else: - await query.message.reply_text(f"✅ Check completed. Found {result} webinar(s)!") + await query.message.reply_text(t(user_id, 'check_completed', count=result)) + +async def language_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + """Handle language selection button clicks.""" + query = update.callback_query + user_id = query.from_user.id + data = query.data + + if data.startswith("lang_"): + lang = data.split("_")[1] + set_user_language(user_id, lang) + await query.answer() + await query.edit_message_text( + t(user_id, 'language_changed'), + parse_mode='HTML' + ) # --- Webinar Checking Job --- @@ -364,16 +529,18 @@ async def check_webinars_job(context: ContextTypes.DEFAULT_TYPE): # Notify subscribers ONLY about NEW webinars if new_webinars: - message = "🎓 Новий вебінар!\n\n" + "\n\n".join([ - f"📌 {w['name']}\n🔗 https://edu.edu.vn.ua{w['url']}" - for w in new_webinars - ]) - subscribers = redis_client.smembers(KEY_SUBSCRIBERS) logger.info(f"Sending notification about {len(new_webinars)} new webinar(s) to {len(subscribers)} subscriber(s)") for sub_id in subscribers: try: + # Build message in user's language + webinar_items = "\n\n".join([ + t(int(sub_id), 'webinar_item', name=w['name'], url=w['url']) + for w in new_webinars + ]) + message = t(int(sub_id), 'webinar_found') + webinar_items + await context.bot.send_message(chat_id=sub_id, text=message, parse_mode='HTML') logger.info(f"Notification sent to {sub_id}") except Exception as e: @@ -403,8 +570,12 @@ def main(): # Handlers app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("help", help_command)) + app.add_handler(CommandHandler("language", language_command)) app.add_handler(CommandHandler("adduser", add_user)) app.add_handler(CommandHandler("removeuser", remove_user)) + + # Callback handlers - language selection first, then admin panel + app.add_handler(CallbackQueryHandler(language_callback, pattern="^lang_")) app.add_handler(CallbackQueryHandler(admin_callback)) # Job Queue diff --git a/userbot/.gitignore b/userbot/.gitignore index 64097b5..fccd91b 100644 --- a/userbot/.gitignore +++ b/userbot/.gitignore @@ -1,5 +1,6 @@ .DS_Store .gitattributes + .vscode /modules/__pycache__/ __pycache__/ @@ -12,14 +13,4 @@ __pycache__/ .venv/ /downloads/ /Downloads/ -/modules/custom_modules -.idea config.ini -unknown_errors.txt -moonlogs.txt -thumb.jpg -antipm_pic.jpg -musicbot/ -.trunk/ -previous_profiles/ -.python-version \ No newline at end of file