from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes, CallbackQueryHandler from datetime import datetime from config import BotConfig from gates import Gates from users import Users, Credential, Role BOT_USERNAME = "lagomareGateKeeperBot" bot_config = BotConfig(BOT_USERNAME) gates = Gates() users = Users() async def post_init(application: Application) -> None: await application.bot.set_my_name(bot_config.name) await application.bot.set_my_description(bot_config.description) await application.bot.set_my_short_description(bot_config.short_description) await application.bot.set_my_commands(bot_config.commands) async def updateuser(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = str(update.effective_user.id) username = update.effective_user.username fullname = update.effective_user.full_name users.update_user(user_id, username, fullname) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = str(update.effective_user.id) role = users.get_role(user_id) keyboard = [] if role == Role.GUEST: # Guests: can request access, or open a gate if granted if users.has_grants(user_id): keyboard.append([InlineKeyboardButton("Open Gate", callback_data="open_gate_menu")]) keyboard.append([InlineKeyboardButton("Request Access", callback_data="request_access")]) elif role == Role.MEMBER: # Members: can open gates and set credentials keyboard.append([InlineKeyboardButton("Open Gate", callback_data="open_gate_menu")]) keyboard.append([InlineKeyboardButton("Set Credentials", callback_data="set_credentials")]) elif role == Role.ADMIN: # Admins: can open gates, grant access, and set credentials keyboard.append([InlineKeyboardButton("Open Gate", callback_data="open_gate_menu")]) keyboard.append([InlineKeyboardButton("Grant Access", callback_data="grant_access")]) keyboard.append([InlineKeyboardButton("Set Credentials", callback_data="set_credentials")]) reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text( "Welcome to GatekeeperBot! Use the buttons below or commands.", reply_markup=reply_markup ) async def setcredentials(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = str(update.effective_user.id) args = context.args if len(args) != 2: return await update.message.reply_text("Usage: `/setcredentials `") role = users.get_role(user_id) if role not in (Role.ADMIN, Role.MEMBER): return await update.message.reply_text("Only members or admins can set credentials") if users.set_credentials(user_id, Credential(args[0], args[1])): await update.message.reply_text("Credentials saved") else: await update.message.reply_text("Error saving credentials") async def opengate(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = str(update.effective_user.id) args = context.args if not args: return await update.message.reply_text("Usage: `/opengate `") gate = args[0] gate_name = gates.get_name(gate) role = users.get_role(user_id) if role in (Role.ADMIN, Role.MEMBER): creds = users.get_credentials(user_id) if not creds: return await update.message.reply_text("Please set your credentials with `/setcredentials` first") elif role == Role.GUEST and users.can_open_gate(user_id, gate): grantor = users.get_grantor(user_id, gate) creds = users.get_credentials(grantor) if not grantor: return await update.message.reply_text("No valid grantor available.") if not creds: return await update.message.reply_text("No valid grantor credentials available.") users.update_grant_last_used(user_id, gate) try: await context.bot.send_message(chat_id=grantor, text=f"Guest {user_id} opened {gate_name}") except Exception as e: print(f"Failed to notify {grantor} that guest {user_id} opened {gate_name}: {e}") else: return await update.message.reply_text("Access denied.") if gates.open_gate(gate, creds): return await update.message.reply_text(f"Gate {gate_name} opened!") await update.message.reply_text(f"ERROR: Cannot open gate {gate_name}") async def open_gate_menu(update: Update, context: ContextTypes.DEFAULT_TYPE): # Show a list of available gates as buttons keyboard = [ [InlineKeyboardButton(gate.name, callback_data=f"opengate_{gate_id}")] for gate_id, gate in gates._gates.items() ] reply_markup = InlineKeyboardMarkup(keyboard) if update.callback_query: await update.callback_query.answer() await update.callback_query.edit_message_text( "Select a gate to open:", reply_markup=reply_markup ) else: await update.message.reply_text("Select a gate to open:", reply_markup=reply_markup) async def handle_gate_open_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query user_id = str(query.from_user.id) data = query.data if data.startswith("opengate_"): gate_id = data[len("opengate_") :] gate_name = gates.get_name(gate_id) role = users.get_role(user_id) if role in (Role.ADMIN, Role.MEMBER): creds = users.get_credentials(user_id) if not creds: await query.answer("Please set your credentials with /setcredentials first", show_alert=True) return elif role == Role.GUEST and users.can_open_gate(user_id, gate_id): grantor = users.get_grantor(user_id, gate_id) creds = users.get_credentials(grantor) if not grantor or not creds: await query.answer("No valid grantor credentials available.", show_alert=True) return users.update_grant_last_used(user_id, gate_id) try: await context.bot.send_message(chat_id=grantor, text=f"Guest {user_id} opened {gate_name}") except Exception as e: print(f"Failed to notify {grantor} that guest {user_id} opened {gate_name}: {e}") else: # TODO: guest not working await query.answer("Access denied.", show_alert=True) return if gates.open_gate(gate_id, creds): await query.answer(f"Gate {gate_name} opened!", show_alert=True) await query.edit_message_text(f"Gate {gate_name} opened!") else: await query.answer(f"ERROR: Cannot open gate {gate_name}", show_alert=True) await query.edit_message_text(f"ERROR: Cannot open gate {gate_name}") async def requestaccess(update: Update, context: ContextTypes.DEFAULT_TYPE): user_id = str(update.effective_user.id) role = users.get_role(user_id) if role != Role.GUEST: return await update.message.reply_text("Only guests can request access.") if not context.args: return await update.message.reply_text("Usage: `/requestaccess`", parse_mode="Markdown") requester = users.get_fullname(user_id) or users.get_username(user_id) text = (f"Access request: {requester} ({user_id}) requests access.\nUse `/approve {user_id} YYYY-MM-DDTHH:MM:SSZ` to grant access.") await update.message.reply_text("Your request has been submitted.") admins = users.get_admins() for admin_id in admins: try: await context.bot.send_message(chat_id=admin_id, text=text, parse_mode="Markdown") except Exception as e: print(f"Failed to notify {admin_id} that guest {user_id} requested access: {e}") async def handle_main_menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query data = query.data if data == "open_gate_menu": await open_gate_menu(update, context) elif data == "request_access": await requestaccess(update, context) async def grantaccess(update: Update, context: ContextTypes.DEFAULT_TYPE): grantor_id = str(update.effective_user.id) if users.get_role(grantor_id) != Role.ADMIN: return await update.message.reply_text("Only admins can grant access.") try: user_id = context.args[0] gate = context.args[1] expires_at = context.args[2] users.grant_access(user_id, gate, datetime.fromisoformat(expires_at.replace("Z", "+00:00")), grantor_id=grantor_id) await update.message.reply_text(f"Access to {gate} granted to user {user_id} until {expires_at}") try: await context.bot.send_message(chat_id=user_id, text=f"Access granted to gate {gate} up to {expires_at}") except Exception as e: print(f"Failed to notify {user_id} that admin {grantor_id} granted access for {gate} up to {expires_at}: {e}") except Exception: await update.message.reply_text("Usage: `/approve `") def main(): app = Application.builder().token(bot_config.token).post_init(post_init).build() app.add_handler(MessageHandler(None, updateuser), group=1) app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("setcredentials", setcredentials)) app.add_handler(CommandHandler("opengate", opengate)) app.add_handler(CommandHandler("requestaccess", requestaccess)) app.add_handler(CommandHandler("grantaccess", grantaccess)) app.add_handler(CallbackQueryHandler(handle_main_menu_callback, pattern="^(open_gate_menu|request_access)$")) app.add_handler(CallbackQueryHandler(handle_gate_open_callback, pattern="^opengate_")) app.run_polling() if __name__ == "__main__": main()