Refactor and start implementing inline keyboards

This commit is contained in:
2025-05-19 23:58:17 +02:00
parent 4a3d2746fb
commit 020b5e0193
7 changed files with 291 additions and 274 deletions

114
bot.py
View File

@@ -1,5 +1,5 @@
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes
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
@@ -23,7 +23,30 @@ async def updateuser(update: Update, context: ContextTypes.DEFAULT_TYPE):
users.update_user(user_id, username, fullname)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Welcome to GatekeeperBot! Use `/setcredentials` to configure your access")
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)
@@ -31,7 +54,7 @@ async def setcredentials(update: Update, context: ContextTypes.DEFAULT_TYPE):
if len(args) != 2:
return await update.message.reply_text("Usage: `/setcredentials <username> <password>`")
role = users.get_role(user_id)
if role not in ("admin", "member"):
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")
@@ -57,7 +80,7 @@ async def opengate(update: Update, context: ContextTypes.DEFAULT_TYPE):
return await update.message.reply_text("No valid grantor available.")
if not creds:
return await update.message.reply_text("No valid grantor credentials available.")
#TODO: update guest last_used_at
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:
@@ -69,10 +92,61 @@ async def opengate(update: Update, context: ContextTypes.DEFAULT_TYPE):
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 not in ("guest"):
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")
@@ -86,21 +160,29 @@ async def requestaccess(update: Update, context: ContextTypes.DEFAULT_TYPE):
except Exception as e:
print(f"Failed to notify {admin_id} that guest {user_id} requested access: {e}")
async def approve(update: Update, context: ContextTypes.DEFAULT_TYPE):
approver_id = str(update.effective_user.id)
if users.get_role(approver_id) != "admin":
return await update.message.reply_text("Only admins can approve access.")
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=approver_id)
await update.message.reply_text(f"Access to {gate} granted to user {user_id}.")
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 {approver_id} approved access for {gate} up to {expires_at}: {e}")
except:
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 <user_id> <gate|all> <expires_at:YYYY-MM-DDTHH:MM:SSZ>`")
def main():
@@ -110,7 +192,9 @@ def main():
app.add_handler(CommandHandler("setcredentials", setcredentials))
app.add_handler(CommandHandler("opengate", opengate))
app.add_handler(CommandHandler("requestaccess", requestaccess))
app.add_handler(CommandHandler("approve", approve))
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__":