mirror of
https://github.com/Noettore/lagomareGateKeeperBot.git
synced 2025-10-14 19:16:40 +02:00
Created modules and organized code
This commit is contained in:
2
LICENSE
2
LICENSE
@@ -1,5 +1,5 @@
|
||||
MIT License
|
||||
Copyright (c) <year> <copyright holders>
|
||||
Copyright (c) 2025 Ettore Dreucci
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
86
README.md
86
README.md
@@ -1,2 +1,86 @@
|
||||
# lagomareGateKeeperBot
|
||||
# Lagomare GateKeeper Bot
|
||||
|
||||
A Telegram bot for managing access to gates, with user roles, credential management, and integration with AVConnect.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Role-based access:** Admins, members, and guests with different permissions.
|
||||
- **Gate control:** Open gates via Telegram commands or inline keyboards.
|
||||
- **Credential management:** Users can set and update their own credentials.
|
||||
- **Access requests:** Guests can request access; admins can grant or revoke it.
|
||||
- **Grant tracking:** Usage of guest grants is tracked.
|
||||
- **Integration:** Connects to AVConnect for actual gate operations.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
lagomareGateKeeperBot/
|
||||
│
|
||||
├── bot.py # Main entry point
|
||||
├── config.py # Bot configuration loader
|
||||
├── handlers/ # Telegram command and callback handlers
|
||||
├── models/ # Data models (users, gates, etc.)
|
||||
├── services/ # External integrations (e.g., AVConnect)
|
||||
├── utils/ # Helper functions
|
||||
├── data/ # Persistent data (users.json, gates.json, etc.)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Clone the repository:**
|
||||
```sh
|
||||
git clone https://git.dreucci.it/noettore/lagomareGateKeeperBot.git
|
||||
cd lagomareGateKeeperBot
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **Configure the bot:**
|
||||
- Edit `data/config.json` with your bot token and settings.
|
||||
- Add gate and user data to `data/gates.json` and `data/users.json`.
|
||||
|
||||
4. **Run the bot:**
|
||||
```sh
|
||||
python bot.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
- `/start` — Show main menu (with inline keyboard based on user role)
|
||||
- `/setcredentials <username> <password>` — Set your AVConnect credentials
|
||||
- `/opengate <gate>` — Open a gate
|
||||
- `/requestaccess` — Guests can request access
|
||||
- `/grantaccess <user_id> <gate|all> <expires_at>` — Admins grant access
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
- Handlers are in the `handlers/` directory, grouped by feature.
|
||||
- Data models are in `models/`.
|
||||
- External services (like AVConnect) are in `services/`.
|
||||
- Utilities and keyboard builders are in `utils/`.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
## Authors
|
||||
|
||||
- [Ettore Dreucci](https://ettore.dreucci.it)
|
@@ -1,45 +0,0 @@
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
class AccessControl:
|
||||
_ACCESS_FILE = "./data/access.json"
|
||||
|
||||
def __init__(self):
|
||||
self._access = self._load_access()
|
||||
|
||||
def _load_access(self) -> dict:
|
||||
try:
|
||||
with open(self._ACCESS_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
def _save_access(self):
|
||||
with open(self._ACCESS_FILE, "w") as f:
|
||||
json.dump(self._access, f)
|
||||
|
||||
def get_grantor(self, user_id: str, gate: str) -> str:
|
||||
access = self._access.get(user_id, {})
|
||||
entry = access.get(gate) or access.get("all")
|
||||
return entry.get("grantor", "") if entry else ""
|
||||
|
||||
def can_open_gate(self, user_id: str, gate: str) -> bool:
|
||||
access = self._access.get(user_id, {})
|
||||
entry = access.get(gate) or access.get("all")
|
||||
if not entry or entry.get("type") != "timed":
|
||||
return False
|
||||
if datetime.now(timezone.utc) < datetime.fromisoformat(entry["expires_at"].replace("Z", "+00:00")):
|
||||
return True
|
||||
return False
|
||||
|
||||
def grant_access(self, user_id: str, gate: str, expires_at: str, grantor_id: str):
|
||||
user_access = self._access.get(user_id, {})
|
||||
user_access[gate] = {
|
||||
"type": "timed",
|
||||
"granted_at": datetime.now(timezone.utc).isoformat(),
|
||||
"expires_at": expires_at,
|
||||
"grantor": grantor_id,
|
||||
"last_used_at": None
|
||||
}
|
||||
self._access[user_id] = user_access
|
||||
self._save_access()
|
205
bot.py
205
bot.py
@@ -1,200 +1,23 @@
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes, CallbackQueryHandler
|
||||
from datetime import datetime
|
||||
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler
|
||||
from functools import partial
|
||||
from config import BotConfig
|
||||
from gates import Gates
|
||||
from users import Users, Credential, Role
|
||||
from models import Gates, Users
|
||||
from handlers import *
|
||||
|
||||
BOT_USERNAME = "lagomareGateKeeperBot"
|
||||
bot_config = BotConfig(BOT_USERNAME)
|
||||
bot_config = BotConfig("lagomareGateKeeperBot")
|
||||
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 = Application.builder().token(bot_config.token).post_init(partial(post_init, bot_config=bot_config)).build()
|
||||
app.add_handler(MessageHandler(None, partial(updateuser, users=users)), group=1)
|
||||
app.add_handler(CommandHandler("start", partial(start, users=users)))
|
||||
app.add_handler(CommandHandler("setcredentials", partial(setcredentials, users=users)))
|
||||
app.add_handler(CommandHandler("opengate", partial(opengate, users=users, gates=gates)))
|
||||
app.add_handler(CommandHandler("requestaccess", partial(requestaccess, users=users)))
|
||||
app.add_handler(CommandHandler("grantaccess", partial(grantaccess, users=users)))
|
||||
app.add_handler(CallbackQueryHandler(partial(handle_main_menu_callback, users=users, gates=gates), pattern="^(open_gate_menu|request_access)$"))
|
||||
app.add_handler(CallbackQueryHandler(partial(handle_gate_open_callback, users=users, gates=gates), pattern="^opengate_"))
|
||||
app.run_polling()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
18
handlers/__init__.py
Normal file
18
handlers/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .access import requestaccess, grantaccess
|
||||
from .credentials import setcredentials
|
||||
from .gates import opengate, open_gate_menu, handle_gate_open_callback
|
||||
from .main import handle_main_menu_callback, post_init, start
|
||||
from .users import updateuser
|
||||
|
||||
__all__ = [
|
||||
"grantaccess",
|
||||
"handle_main_menu_callback",
|
||||
"handle_gate_open_callback",
|
||||
"opengate",
|
||||
"open_gate_menu",
|
||||
"post_init",
|
||||
"requestaccess",
|
||||
"setcredentials",
|
||||
"start",
|
||||
"updateuser"
|
||||
]
|
38
handlers/access.py
Normal file
38
handlers/access.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from datetime import datetime
|
||||
from models import Users, Role
|
||||
|
||||
async def requestaccess(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users):
|
||||
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 grantaccess(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users):
|
||||
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>`")
|
16
handlers/credentials.py
Normal file
16
handlers/credentials.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from models import Users, Credential, Role
|
||||
|
||||
async def setcredentials(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users):
|
||||
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")
|
85
handlers/gates.py
Normal file
85
handlers/gates.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import ContextTypes
|
||||
from models import Gates, Users, Role
|
||||
|
||||
async def opengate(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users, gates: Gates):
|
||||
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, gates: Gates):
|
||||
# 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, users: Users, gates: Gates):
|
||||
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}")
|
46
handlers/main.py
Normal file
46
handlers/main.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Application, ContextTypes
|
||||
from .gates import open_gate_menu
|
||||
from .access import requestaccess
|
||||
from models import Gates, Users, Role
|
||||
from config import BotConfig
|
||||
|
||||
async def post_init(application: Application, bot_config: BotConfig) -> 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 start(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users):
|
||||
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 handle_main_menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users, gates: Gates):
|
||||
query = update.callback_query
|
||||
data = query.data
|
||||
if data == "open_gate_menu":
|
||||
await open_gate_menu(update, context, gates=gates)
|
||||
elif data == "request_access":
|
||||
await requestaccess(update, context, users=users)
|
9
handlers/users.py
Normal file
9
handlers/users.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from models import Users
|
||||
|
||||
async def updateuser(update: Update, context: ContextTypes.DEFAULT_TYPE, users: Users):
|
||||
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)
|
12
models/__init__.py
Normal file
12
models/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from .credential import Credential
|
||||
from .gates import Gates
|
||||
from .status import Status
|
||||
from .users import Users, Role
|
||||
|
||||
__all__ = [
|
||||
"Credential",
|
||||
"Gates",
|
||||
"Status",
|
||||
"Users",
|
||||
"Role"
|
||||
]
|
@@ -1,9 +1,3 @@
|
||||
from enum import Enum
|
||||
|
||||
class Status(Enum):
|
||||
ENABLED = 1
|
||||
DISABLED = 0
|
||||
|
||||
class Credential:
|
||||
def __init__(self, username: str, password: str):
|
||||
self.username = username
|
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
from avconnect import AVConnectAPI
|
||||
from commons import Status, Credential
|
||||
from services import AVConnectAPI
|
||||
from .status import Status
|
||||
from .credential import Credential
|
||||
|
||||
class Gate:
|
||||
def __init__(self, id: str, name: str, status: Status = Status.ENABLED):
|
5
models/status.py
Normal file
5
models/status.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from enum import Enum
|
||||
|
||||
class Status(Enum):
|
||||
ENABLED = 1
|
||||
DISABLED = 0
|
@@ -1,7 +1,8 @@
|
||||
import json
|
||||
from enum import Enum
|
||||
from datetime import datetime, timezone
|
||||
from commons import Status, Credential
|
||||
from .status import Status
|
||||
from .credential import Credential
|
||||
|
||||
class Role(Enum):
|
||||
ADMIN = "admin"
|
5
services/__init__.py
Normal file
5
services/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .avconnect import AVConnectAPI
|
||||
|
||||
__all__ = [
|
||||
"AVConnectAPI"
|
||||
]
|
@@ -1,6 +1,6 @@
|
||||
import requests
|
||||
from fake_useragent import UserAgent
|
||||
from commons import Credential
|
||||
from models import Credential
|
||||
|
||||
class AVConnectAPI:
|
||||
_BASE_URL = "https://www.avconnect.it"
|
Reference in New Issue
Block a user