Files
lagomareGateKeeperBot/bot.py

202 lines
9.7 KiB
Python

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 <username> <password>`")
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>`")
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} <gate|all> 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 <user_id> <gate|all> <expires_at:YYYY-MM-DDTHH:MM:SSZ>`")
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()