diff --git a/.gitignore b/.gitignore index 41f6e38..49b651c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ sync.ffs_lock .sync.ffs_db +# Copyparty +*.hist/ + # Volumes and data directories gitea/gitea-db/ gitea/gitea-data/* @@ -88,4 +91,4 @@ temp/* .env.anna .env.forust .env.* -!*example +!*example \ No newline at end of file diff --git a/edu_master/webinar-checker/checker.py b/edu_master/webinar-checker/checker.py index 2efd86a..60c3dbb 100644 --- a/edu_master/webinar-checker/checker.py +++ b/edu_master/webinar-checker/checker.py @@ -3,7 +3,8 @@ import logging import redis import json -from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ChatMember +from telegram.constants import ChatType from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes from playwright.async_api import async_playwright @@ -57,7 +58,7 @@ TRANSLATIONS = { 'welcome_admin': "\n\n👑 Режим администратора активен", 'access_denied': "⛔ Доступ запрещен. Вас нет в белом списке.", 'help_title': "🤖 Помощь по боту\n\n", - 'help_commands': "/start - Подписаться на уведомления\n/help - Показать это сообщение\n/language - Сменить язык", + 'help_commands': "/start - Подписаться на уведомления\n/stop - Отписаться от уведомлений\n/help - Показать это сообщение\n/language - Сменить язык", 'help_admin': "\nКоманды администратора:\n/adduser [user_id] - Добавить пользователя в белый список\n/removeuser [user_id] - Удалить пользователя из белого списка\nИли используйте панель ниже для управления настройками.", 'admin_only': "⛔ Только для администратора!", 'user_added': "✅ Пользователь {user_id} добавлен в белый список", @@ -94,7 +95,7 @@ TRANSLATIONS = { 'welcome_admin': "\n\n👑 Режим адміністратора активний", 'access_denied': "⛔ Доступ заборонено. Вас немає в білому списку.", 'help_title': "🤖 Довідка по боту\n\n", - 'help_commands': "/start - Підписатися на сповіщення\n/help - Показати це повідомлення\n/language - Змінити мову", + 'help_commands': "/start - Підписатися на сповіщення\n/stop - Відписатися від сповіщень\n/help - Показати це повідомлення\n/language - Змінити мову", 'help_admin': "\nКоманди адміністратора:\n/adduser [user_id] - Додати користувача до білого списку\n/removeuser [user_id] - Видалити користувача з білого списку\nАбо використовуйте панель нижче для керування налаштуваннями.", 'admin_only': "⛔ Тільки для адміністратора!", 'user_added': "✅ Користувач {user_id} доданий до білого списку", @@ -131,7 +132,7 @@ TRANSLATIONS = { '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_commands': "/start - Subscribe to notifications\n/stop - Unsubscribe from 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", @@ -212,6 +213,21 @@ def is_whitelisted(user_id: int) -> bool: return redis_client.sismember(KEY_WHITELIST, str(user_id)) +async def is_group_admin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool: + """Check if the user is an administrator in the group.""" + user = update.effective_user + chat = update.effective_chat + + if chat.type in [ChatType.PRIVATE, "private"]: + return True + + try: + member = await context.bot.get_chat_member(chat.id, user.id) + return member.status in [ChatMember.OWNER, ChatMember.ADMINISTRATOR] + except Exception as e: + logger.error(f"Failed to check admin status: {e}") + return False + def get_admin_keyboard(user_id: int): """Generate admin panel keyboard.""" whitelist_enabled = redis_client.get(KEY_WHITELIST_ENABLED) != "0" @@ -230,19 +246,21 @@ def get_admin_keyboard(user_id: int): async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /start command.""" user = update.effective_user - logger.info(f"User {user.id} ({user.username}) started the bot.") + chat = update.effective_chat + logger.info(f"User {user.id} ({user.username}) started the bot in chat {chat.id} ({chat.type}).") + # Check whitelist - MUST be the user executing the command if not is_whitelisted(user.id): await update.message.reply_text(t(user.id, 'access_denied')) return - # Add to subscribers - redis_client.sadd(KEY_SUBSCRIBERS, user.id) + # Add to subscribers (Chat ID!) + redis_client.sadd(KEY_SUBSCRIBERS, chat.id) - msg = t(user.id, 'welcome', name=user.first_name) + msg = t(chat.id, 'welcome', name=user.first_name) - if user.id == ADMIN_ID: - msg += t(user.id, 'welcome_admin') + if user.id == ADMIN_ID and chat.type == "private": + msg += t(chat.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') @@ -250,19 +268,39 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /help command.""" user_id = update.effective_user.id - msg = t(user_id, 'help_title') + t(user_id, 'help_commands') + chat_id = update.effective_chat.id + msg = t(chat_id, 'help_title') + t(chat_id, 'help_commands') - if user_id == ADMIN_ID: - msg += t(user_id, 'help_admin') + if user_id == ADMIN_ID and update.effective_chat.type == "private": + msg += t(chat_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 stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """Handle /stop command (unsubscribe).""" + user = update.effective_user + chat = update.effective_chat + + # Permission check: Whitelisted user OR Group Admin + if not (is_whitelisted(user.id) or await is_group_admin(update, context)): + await update.message.reply_text(t(chat.id, 'access_denied')) # Or specific "admin only" message + return + + redis_client.srem(KEY_SUBSCRIBERS, chat.id) + await update.message.reply_text(t(chat.id, 'whitelist_disabled').replace(" whitelist", " notifications").replace("Білий список", "Сповіщення").replace("Белый список", "Уведомления") if chat.id else "Unsubscribed") + async def language_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /language command.""" - user_id = update.effective_user.id + user = update.effective_user + chat = update.effective_chat + + # Permission check for groups + if not (is_whitelisted(user.id) or await is_group_admin(update, context)): + return + await update.message.reply_text( - t(user_id, 'select_language'), + t(chat.id, 'select_language'), parse_mode='HTML', reply_markup=get_language_keyboard() ) @@ -544,6 +582,7 @@ async def check_webinars_job(context: ContextTypes.DEFAULT_TYPE): for sub_id in subscribers: try: # Build message in user's language + # sub_id comes from redis set as string, convert to int for translation lookup webinar_items = "\n\n".join([ t(int(sub_id), 'webinar_item', name=w['name'], url=w['url']) for w in new_webinars @@ -578,6 +617,7 @@ def main(): # Handlers app.add_handler(CommandHandler("start", start)) + app.add_handler(CommandHandler("stop", stop_command)) app.add_handler(CommandHandler("help", help_command)) app.add_handler(CommandHandler("language", language_command)) app.add_handler(CommandHandler("adduser", add_user))