mirror of
https://github.com/Noettore/lagomareGateKeeperBot.git
synced 2025-10-15 03:26:40 +02:00
Complete refactor... Some issue...
This commit is contained in:
@@ -1,40 +1,45 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
ACCESS_FILE = "./data/access.json"
|
class AccessControl:
|
||||||
|
_ACCESS_FILE = "./data/access.json"
|
||||||
|
|
||||||
def load_access():
|
def __init__(self):
|
||||||
try:
|
self._access = self._load_access()
|
||||||
with open(ACCESS_FILE, "r") as f:
|
|
||||||
return json.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def save_access(data):
|
def _load_access(self) -> dict:
|
||||||
with open(ACCESS_FILE, "w") as f:
|
try:
|
||||||
json.dump(data, f)
|
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(user_id, gate):
|
def get_grantor(self, user_id: str, gate: str) -> str:
|
||||||
access = load_access().get(str(user_id), {})
|
access = self._access.get(user_id, {})
|
||||||
entry = access.get(gate) or access.get("all")
|
entry = access.get(gate) or access.get("all")
|
||||||
return entry.get("grantor", "")
|
return entry.get("grantor", "")
|
||||||
|
|
||||||
def can_open_gate(user_id, gate):
|
def can_open_gate(self, user_id: str, gate: str) -> bool:
|
||||||
access = load_access().get(str(user_id), {})
|
access = self._access.get(user_id, {})
|
||||||
entry = access.get(gate) or access.get("all")
|
entry = access.get(gate) or access.get("all")
|
||||||
if not entry or entry["type"] != "timed":
|
if not entry or entry["type"] != "timed":
|
||||||
|
return False
|
||||||
|
if datetime.now(timezone.utc) < datetime.fromisoformat(entry["expires_at"].replace("Z", "+00:00")):
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
if datetime.now(timezone.utc) < datetime.fromisoformat(entry["expires_at"].replace("Z", "+00:00")):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def grant_access(user_id, gate, expires_at, grantor_id):
|
def grant_access(self, user_id: str, gate: str, expires_at: str, grantor_id: str):
|
||||||
access = load_access()
|
user_access = self._access.get(user_id, {})
|
||||||
user_access = access.get(str(user_id), {})
|
user_access[gate] = {
|
||||||
user_access[gate] = {
|
"type": "timed",
|
||||||
"type": "timed",
|
"granted_at": datetime.now(timezone.utc).isoformat(),
|
||||||
"expires_at": expires_at,
|
"expires_at": expires_at,
|
||||||
"grantor": grantor_id
|
"grantor": grantor_id,
|
||||||
}
|
"last_used_at": None
|
||||||
access[str(user_id)] = user_access
|
}
|
||||||
save_access(access)
|
self._access[user_id] = user_access
|
||||||
|
self._save_access()
|
||||||
|
40
avconnect.py
40
avconnect.py
@@ -1,42 +1,46 @@
|
|||||||
import requests
|
import requests
|
||||||
import pprint
|
import pprint
|
||||||
|
from fake_useragent import UserAgent
|
||||||
BASE_URL = "https://www.avconnect.it"
|
from commons import Credential
|
||||||
|
|
||||||
class AVConnectAPI:
|
class AVConnectAPI:
|
||||||
def __init__(self, username: str, password: str):
|
_BASE_URL = "https://www.avconnect.it"
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.session = requests.Session()
|
|
||||||
self.authenticated = False
|
|
||||||
|
|
||||||
def authenticate(self) -> bool:
|
def __init__(self, credentials: Credential):
|
||||||
login_url = f"{BASE_URL}/loginone.php"
|
self._ua = UserAgent(browsers=["Chrome Mobile"], os=["Android"], platforms=["mobile"]).random
|
||||||
|
self._username = credentials.username
|
||||||
|
self._password = credentials.password
|
||||||
|
self._session = requests.Session()
|
||||||
|
self._authenticated = False
|
||||||
|
|
||||||
|
def _authenticate(self) -> bool:
|
||||||
|
login_url = f"{self._BASE_URL}/loginone.php"
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
}
|
}
|
||||||
payload = f"userid={self.username}&password={self.password}&entra=Login"
|
payload = f"userid={self._username}&password={self._password}&entra=Login"
|
||||||
response = self.session.post(login_url, data=payload, headers=headers)
|
response = self._session.post(login_url, data=payload, headers=headers)
|
||||||
|
|
||||||
if response.ok and "PHPSESSID" in self.session.cookies:
|
if response.ok and "PHPSESSID" in self._session.cookies:
|
||||||
self.authenticated = True
|
self._authenticated = True
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def exec_gate_macro(self, id_macro) -> requests.Response:
|
def exec_gate_macro(self, id_macro) -> bool:
|
||||||
if not self.authenticated and not self.authenticate():
|
if not self._authenticated and not self._authenticate():
|
||||||
raise Exception("Authentication failed.")
|
raise Exception("Authentication failed.")
|
||||||
|
|
||||||
exec_url = f"{BASE_URL}/exemacrocom.php"
|
exec_url = f"{self._BASE_URL}/exemacrocom.php"
|
||||||
headers = {
|
headers = {
|
||||||
|
"User-Agent": self._ua,
|
||||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
}
|
}
|
||||||
payload = f"idmacrocom={id_macro}&nome=16"
|
payload = f"idmacrocom={id_macro}&nome=16"
|
||||||
|
|
||||||
response = self.session.prepare_request(requests.Request("POST", exec_url, data=payload, headers=headers))
|
response = self._session.prepare_request(requests.Request("POST", exec_url, data=payload, headers=headers))
|
||||||
pprint.pprint(response.headers)
|
pprint.pprint(response.headers)
|
||||||
return True
|
return True
|
||||||
#response = self.session.post(exec_url, data=payload, headers=headers)
|
#response = self._session.post(exec_url, data=payload, headers=headers)
|
||||||
#if response.ok:
|
#if response.ok:
|
||||||
# return True
|
# return True
|
||||||
#return False
|
#return False
|
86
bot.py
86
bot.py
@@ -1,38 +1,42 @@
|
|||||||
from telegram import Update
|
from telegram import Update
|
||||||
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes
|
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes
|
||||||
from config import get_bot_token, get_commands
|
from config import BotConfig
|
||||||
from access_control import can_open_gate, grant_access, get_grantor
|
from gates import Gates
|
||||||
from gates import get_name, open_gate
|
from users import Users, Credential, Role
|
||||||
from users import get_role, set_credentials, get_credentials, get_grantor_credentials, get_admins, update_user, get_fullname, get_username
|
|
||||||
|
|
||||||
BOT_NAME = "lagomareGateKeeperBot"
|
BOT_USERNAME = "lagomareGateKeeperBot"
|
||||||
|
bot_config = BotConfig(BOT_USERNAME)
|
||||||
|
gates = Gates()
|
||||||
|
users = Users()
|
||||||
|
|
||||||
async def post_init(application: Application) -> None:
|
async def post_init(application: Application) -> None:
|
||||||
bot_commands = get_commands(BOT_NAME)
|
bot_commands = bot_config.commands
|
||||||
await application.bot.set_my_commands(bot_commands)
|
await application.bot.set_my_commands(bot_commands)
|
||||||
await application.bot.set_my_name("Lagomare GateKeeper Bot")
|
await application.bot.set_my_name(bot_config.name)
|
||||||
await application.bot.set_my_description("This bot is used to open Lagomare gates by members. You can also request timed access if you are a guest.")
|
await application.bot.set_my_description(bot_config.description)
|
||||||
await application.bot.set_my_short_description("Open Lagomare gates or request guest access")
|
await application.bot.set_my_short_description(bot_config.short_description)
|
||||||
|
|
||||||
async def updateuser(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def updateuser(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
user_id = str(update.effective_user.id)
|
user_id = str(update.effective_user.id)
|
||||||
username = update.effective_user.username
|
username = update.effective_user.username
|
||||||
fullname = update.effective_user.full_name
|
fullname = update.effective_user.full_name
|
||||||
update_user(user_id, username, fullname)
|
users.update_user(user_id, username, fullname)
|
||||||
|
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
await update.message.reply_text("Welcome to GatekeeperBot! Use `/setcredentials` to configure your access.")
|
await update.message.reply_text("Welcome to GatekeeperBot! Use `/setcredentials` to configure your access")
|
||||||
|
|
||||||
async def setcredentials(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def setcredentials(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
user_id = str(update.effective_user.id)
|
user_id = str(update.effective_user.id)
|
||||||
args = context.args
|
args = context.args
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
return await update.message.reply_text("Usage: `/setcredentials <username> <password>`")
|
return await update.message.reply_text("Usage: `/setcredentials <username> <password>`")
|
||||||
role = get_role(user_id)
|
role = users.get_role(user_id)
|
||||||
if role not in ("admin", "member"):
|
if role not in ("admin", "member"):
|
||||||
return await update.message.reply_text("Only members or admins can set credentials.")
|
return await update.message.reply_text("Only members or admins can set credentials")
|
||||||
set_credentials(user_id, args[0], args[1])
|
if users.set_credentials(user_id, Credential(args[0], args[1])):
|
||||||
await update.message.reply_text("Credentials saved.")
|
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):
|
async def opengate(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
user_id = str(update.effective_user.id)
|
user_id = str(update.effective_user.id)
|
||||||
@@ -40,55 +44,57 @@ async def opengate(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
if not args:
|
if not args:
|
||||||
return await update.message.reply_text("Usage: `/opengate <gate>`")
|
return await update.message.reply_text("Usage: `/opengate <gate>`")
|
||||||
gate = args[0]
|
gate = args[0]
|
||||||
role = get_role(user_id)
|
gate_name = gates.get_name(gate)
|
||||||
if role in ("admin", "member"):
|
role = users.get_role(user_id)
|
||||||
creds = get_credentials(user_id)
|
if role in (Role.ADMIN, Role.MEMBER):
|
||||||
|
creds = users.get_credentials(user_id)
|
||||||
if not creds:
|
if not creds:
|
||||||
return await update.message.reply_text("Please set your credentials with `/setcredentials` first.")
|
return await update.message.reply_text("Please set your credentials with `/setcredentials` first")
|
||||||
elif role == "guest" and can_open_gate(user_id, gate):
|
elif role == Role.GUEST and users.can_open_gate(user_id, gate):
|
||||||
creds = get_grantor_credentials(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:
|
if not creds:
|
||||||
return await update.message.reply_text("No valid grantor credentials available.")
|
return await update.message.reply_text("No valid grantor credentials available.")
|
||||||
grantor = get_grantor(user_id, gate)
|
#TODO: update guest last_used_at
|
||||||
if grantor:
|
try:
|
||||||
try:
|
await context.bot.send_message(chat_id=grantor, text=f"Guest {user_id} opened {gate_name}")
|
||||||
await context.bot.send_message(chat_id=grantor, text=f"Guest {user_id} opened {get_name(gate)}")
|
except Exception as e:
|
||||||
except Exception as e:
|
print(f"Failed to notify {grantor} that guest {user_id} opened {gate_name}: {e}")
|
||||||
print(f"Failed to notify {grantor} that guest {user_id} opened {get_name(gate)}: {e}")
|
|
||||||
else:
|
else:
|
||||||
return await update.message.reply_text("Access denied.")
|
return await update.message.reply_text("Access denied.")
|
||||||
|
|
||||||
if open_gate(gate, creds):
|
if gates.open_gate(gate, creds):
|
||||||
return await update.message.reply_text(f"Gate {get_name(gate)} opened!")
|
return await update.message.reply_text(f"Gate {gate_name} opened!")
|
||||||
await update.message.reply_text(f"ERROR: Cannot open gate {get_name(gate)}")
|
await update.message.reply_text(f"ERROR: Cannot open gate {gate_name}")
|
||||||
|
|
||||||
async def requestaccess(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def requestaccess(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
user_id = str(update.effective_user.id)
|
user_id = str(update.effective_user.id)
|
||||||
role = get_role(user_id)
|
role = users.get_role(user_id)
|
||||||
if role not in ("guest"):
|
if role not in ("guest"):
|
||||||
return await update.message.reply_text("Only guests can request access.")
|
return await update.message.reply_text("Only guests can request access.")
|
||||||
if not context.args:
|
if not context.args:
|
||||||
return await update.message.reply_text("Usage: `/requestaccess <gate>`")
|
return await update.message.reply_text("Usage: `/requestaccess`", parse_mode="Markdown")
|
||||||
gate = context.args[0]
|
requester = users.get_fullname(user_id) or users.get_username(user_id)
|
||||||
requester = get_fullname(user_id) or 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.")
|
||||||
text = (f"Access request: {requester} ({user_id}) requests access to *{gate}*.\nUse `/approve {user_id} {gate} 60` to grant access.")
|
|
||||||
await update.message.reply_text("Your request has been submitted.")
|
await update.message.reply_text("Your request has been submitted.")
|
||||||
admins = get_admins()
|
admins = users.get_admins()
|
||||||
for admin_id in admins:
|
for admin_id in admins:
|
||||||
try:
|
try:
|
||||||
await context.bot.send_message(chat_id=admin_id, text=text, parse_mode="Markdown")
|
await context.bot.send_message(chat_id=admin_id, text=text, parse_mode="Markdown")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to notify {admin_id} that guest {user_id} requested access for {gate}: {e}")
|
print(f"Failed to notify {admin_id} that guest {user_id} requested access: {e}")
|
||||||
|
|
||||||
async def approve(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def approve(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
approver_id = str(update.effective_user.id)
|
approver_id = str(update.effective_user.id)
|
||||||
if get_role(approver_id) != "admin":
|
if users.get_role(approver_id) != "admin":
|
||||||
return await update.message.reply_text("Only admins can approve access.")
|
return await update.message.reply_text("Only admins can approve access.")
|
||||||
try:
|
try:
|
||||||
user_id = context.args[0]
|
user_id = context.args[0]
|
||||||
gate = context.args[1]
|
gate = context.args[1]
|
||||||
expires_at = context.args[2]
|
expires_at = context.args[2]
|
||||||
grant_access(user_id, gate, expires_at, grantor_id=approver_id)
|
users.grant_access(user_id, gate, expires_at, grantor_id=approver_id)
|
||||||
await update.message.reply_text(f"Access to {gate} granted to user {user_id}.")
|
await update.message.reply_text(f"Access to {gate} granted to user {user_id}.")
|
||||||
try:
|
try:
|
||||||
await context.bot.send_message(chat_id=user_id, text=f"Access granted to gate {gate} up to {expires_at}")
|
await context.bot.send_message(chat_id=user_id, text=f"Access granted to gate {gate} up to {expires_at}")
|
||||||
@@ -98,7 +104,7 @@ async def approve(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
await update.message.reply_text("Usage: `/approve <user_id> <gate|all> <expires_at:YYYY-MM-DDTHH:MM:SSZ>`")
|
await update.message.reply_text("Usage: `/approve <user_id> <gate|all> <expires_at:YYYY-MM-DDTHH:MM:SSZ>`")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = Application.builder().token(get_bot_token(BOT_NAME)).post_init(post_init).build()
|
app = Application.builder().token(bot_config.token).post_init(post_init).build()
|
||||||
app.add_handler(MessageHandler(None, updateuser), group=1)
|
app.add_handler(MessageHandler(None, updateuser), group=1)
|
||||||
app.add_handler(CommandHandler("start", start))
|
app.add_handler(CommandHandler("start", start))
|
||||||
app.add_handler(CommandHandler("setcredentials", setcredentials))
|
app.add_handler(CommandHandler("setcredentials", setcredentials))
|
||||||
|
32
commons.py
Normal file
32
commons.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class Status(Enum):
|
||||||
|
ENABLED = 1
|
||||||
|
DISABLED = 0
|
||||||
|
|
||||||
|
class Credential:
|
||||||
|
def __init__(self, username: str, password: str):
|
||||||
|
self._username: str = username
|
||||||
|
self._password: str = password
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
return {
|
||||||
|
"username": self._username,
|
||||||
|
"password": self._password
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self) -> str:
|
||||||
|
return self._username
|
||||||
|
|
||||||
|
@username.setter
|
||||||
|
def username(self, username: str):
|
||||||
|
self._username = username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password(self) -> str:
|
||||||
|
return self._password
|
||||||
|
|
||||||
|
@password.setter
|
||||||
|
def password(self, password: str):
|
||||||
|
self._password = password
|
59
config.py
59
config.py
@@ -1,21 +1,50 @@
|
|||||||
import json
|
import json
|
||||||
from telegram import BotCommand
|
from telegram import BotCommand
|
||||||
|
|
||||||
ACCESS_FILE = "./data/config.json"
|
class BotConfig:
|
||||||
|
|
||||||
def load_config():
|
def __init__(self, bot_username: str, json_path: str = "./data/config.json"):
|
||||||
try:
|
self._json_path: str = json_path
|
||||||
with open(ACCESS_FILE, "r") as f:
|
self._bot_username: str = bot_username
|
||||||
return json.load(f)
|
self._token: str = ""
|
||||||
except FileNotFoundError:
|
self._name: str = ""
|
||||||
return {}
|
self._description: str = ""
|
||||||
|
self._short_description: str = ""
|
||||||
|
self._commands: list[BotCommand] = []
|
||||||
|
|
||||||
def get_bot_token(bot_name):
|
self._load_config()
|
||||||
config = load_config().get(str(bot_name), {})
|
|
||||||
return config.get("token", "")
|
def _load_config(self):
|
||||||
|
try:
|
||||||
|
with open(self._json_path, "r") as f:
|
||||||
|
config = json.load(f).get(self._bot_username, {})
|
||||||
|
self._token = config.get("token", "")
|
||||||
|
self._name = config.get("name", "")
|
||||||
|
self._description = config.get("description", "")
|
||||||
|
self._short_description = config.get("short_description", "")
|
||||||
|
self._commands = [
|
||||||
|
BotCommand(command, description)
|
||||||
|
for command, description in config.get("commands", {}).items()
|
||||||
|
]
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_commands(bot_name):
|
@property
|
||||||
config = load_config().get(str(bot_name), {})
|
def token(self) -> str:
|
||||||
commands = config.get("commands", {})
|
return self._token
|
||||||
bot_commands = []
|
|
||||||
return [BotCommand(command, description) for command, description in commands.items()]
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_description(self) -> str:
|
||||||
|
return self._short_description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def commands(self) -> list[BotCommand]:
|
||||||
|
return self._commands
|
67
gates.py
67
gates.py
@@ -1,27 +1,54 @@
|
|||||||
import json
|
import json
|
||||||
from avconnect import AVConnectAPI
|
from avconnect import AVConnectAPI
|
||||||
|
from commons import Status, Credential
|
||||||
|
|
||||||
GATE_FILE = "./data/gates.json"
|
class _Gate:
|
||||||
|
def __init__(self, id: str, name: str, status: int | Status = Status.ENABLED):
|
||||||
|
self._id: str = id
|
||||||
|
self._name: str = name
|
||||||
|
self._status: Status = status if isinstance(status, Status) else Status(status)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> str:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> Status:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@status.setter
|
||||||
|
def status(self, status: int | Status):
|
||||||
|
self._status = status if isinstance(status, Status) else Status(status)
|
||||||
|
|
||||||
def load_gates():
|
class Gates:
|
||||||
try:
|
|
||||||
with open(GATE_FILE, "r") as f:
|
|
||||||
return json.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_name(gate):
|
def __init__(self, json_path: str = "./data/gates.json"):
|
||||||
return load_gates().get(str(gate), {}).get("name", "")
|
self._json_path: str = json_path
|
||||||
|
self._gates: dict[str, _Gate] = self._load_gates()
|
||||||
|
|
||||||
def open_gate(gate, credentials):
|
def _load_gates(self):
|
||||||
gate_info = load_gates().get(str(gate), {})
|
try:
|
||||||
if not gate_info:
|
with open(self._json_path, "r") as file:
|
||||||
return False
|
gates_data = json.load(file)
|
||||||
gate_id = gate_info["id"]
|
return {gate: _Gate(data["id"], data["name"]) for gate, data in gates_data.items()}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
try:
|
def get_name(self, gate: str) -> str:
|
||||||
api = AVConnectAPI(credentials["username"], credentials["password"])
|
return self._gates.get(gate, {}).name
|
||||||
return api.exec_gate_macro(gate_id)
|
|
||||||
except Exception as e:
|
def open_gate(self, gate: str, credentials: Credential) -> bool:
|
||||||
print(f"Failed to open gate {gate}: {e}")
|
if gate not in self._gates.keys():
|
||||||
return False
|
return False
|
||||||
|
if self._gates[gate].status == Status.DISABLED:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
api = AVConnectAPI(credentials)
|
||||||
|
return api.exec_gate_macro(self._gates[gate].id)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to open gate {gate}: {e}")
|
||||||
|
return False
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
python-telegram-bot==20.7
|
python-telegram-bot==20.7
|
||||||
|
fake-useragent==2.2.0
|
256
users.py
256
users.py
@@ -1,65 +1,213 @@
|
|||||||
import json
|
import json
|
||||||
|
from enum import Enum
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from commons import Status, Credential
|
||||||
|
|
||||||
USER_FILE = "./data/users.json"
|
class Role(Enum):
|
||||||
|
ADMIN = "admin"
|
||||||
|
MEMBER = "member"
|
||||||
|
GUEST = "guest"
|
||||||
|
|
||||||
def load_users():
|
class _Grant:
|
||||||
try:
|
def __init__(self, grantor: str, expires_at: datetime, granted_at: datetime = datetime.now(), last_used_at: datetime = None, status: int | Status = Status.ENABLED):
|
||||||
with open(USER_FILE, "r") as f:
|
self._grantor: str = grantor
|
||||||
return json.load(f)
|
self._granted_at: datetime = granted_at
|
||||||
except FileNotFoundError:
|
self._expires_at = expires_at
|
||||||
return {}
|
self._last_used_at = last_used_at
|
||||||
|
self._status: Status = status
|
||||||
def save_users(data):
|
|
||||||
with open(USER_FILE, "w") as f:
|
|
||||||
json.dump(data, f)
|
|
||||||
|
|
||||||
def update_user(user_id, username, fullname):
|
|
||||||
has_changes = False
|
|
||||||
users = load_users()
|
|
||||||
user = users.get(str(user_id), {})
|
|
||||||
|
|
||||||
if user.get("username", "") != username:
|
def __dict__(self):
|
||||||
has_changes = True
|
return {
|
||||||
user["username"] = username
|
"grantor": self._grantor,
|
||||||
if user.get("fullname", "") != fullname:
|
"granted_at": self._granted_at.isoformat(),
|
||||||
has_changes = True
|
"expires_at": self._expires_at.isoformat(),
|
||||||
user["fullname"] = fullname
|
"last_used_at": self._last_used_at.isoformat() if self._last_used_at else None,
|
||||||
if not user.get("role"):
|
"status": self._status.value
|
||||||
has_changes = True
|
}
|
||||||
user["role"] = "guest"
|
|
||||||
|
@property
|
||||||
|
def grantor(self) -> str:
|
||||||
|
return self._grantor
|
||||||
|
|
||||||
if has_changes:
|
@grantor.setter
|
||||||
users[str(user_id)] = user
|
def grantor(self, grantor: str):
|
||||||
save_users(users)
|
self._grantor = grantor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def granted_at(self) -> datetime:
|
||||||
|
return self._granted_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expires_at(self) -> datetime:
|
||||||
|
return self._expires_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_used_at(self) -> datetime:
|
||||||
|
return self._last_used_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> Status:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
class _User:
|
||||||
|
def __init__(self, id: str, username: str, fullname: str, role: str | Role = Role.GUEST, credentials: dict = None, grants: dict = None, status: int | Status = Status.ENABLED):
|
||||||
|
self._id: str = id
|
||||||
|
self._username: str = username
|
||||||
|
self._fullname: str = fullname
|
||||||
|
self._role: Role = role if isinstance(role, Role) else Role(role)
|
||||||
|
self._credentials: Credential = Credential(**credentials)
|
||||||
|
self._grants: dict[str, _Grant] = {gate: _Grant(**grant) for gate, grant in grants.items()} if grants else {}
|
||||||
|
self._status: Status = status if isinstance(status, Status) else Status(status)
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
return {
|
||||||
|
"id": self._id,
|
||||||
|
"username": self._username,
|
||||||
|
"fullname": self._fullname,
|
||||||
|
"role": self._role.value,
|
||||||
|
"credentials": self._credentials.__dict__(),
|
||||||
|
"grants": {gate: grant.__dict__() for gate, grant in self._grants.items()},
|
||||||
|
"status": self._status.value
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> str:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self) -> str:
|
||||||
|
return self._username
|
||||||
|
|
||||||
|
@username.setter
|
||||||
|
def username(self, username: str):
|
||||||
|
self._username = username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fullname(self) -> str:
|
||||||
|
return self._fullname
|
||||||
|
|
||||||
|
@fullname.setter
|
||||||
|
def fullname(self, fullname: str):
|
||||||
|
self._fullname = fullname
|
||||||
|
|
||||||
|
@property
|
||||||
|
def role(self) -> Role:
|
||||||
|
return self._role
|
||||||
|
|
||||||
|
@role.setter
|
||||||
|
def role(self, role: str | Role):
|
||||||
|
self._role = role if isinstance(role, Role) else Role(role)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def credentials(self) -> Credential:
|
||||||
|
return self._credentials
|
||||||
|
|
||||||
|
@credentials.setter
|
||||||
|
def credentials(self, credentials: Credential):
|
||||||
|
self._credentials = credentials
|
||||||
|
|
||||||
|
@property
|
||||||
|
def grants(self) -> dict[str, _Grant]:
|
||||||
|
return self._grants
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> Status:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@status.setter
|
||||||
|
def status(self, status: int | Status):
|
||||||
|
self._status = status if isinstance(status, Status) else Status(status)
|
||||||
|
|
||||||
|
def grant(self, gate: str, grant: _Grant):
|
||||||
|
self._grants[gate] = grant
|
||||||
|
|
||||||
|
def revoke(self, gate: str):
|
||||||
|
self._grants.pop(gate, None)
|
||||||
|
|
||||||
|
|
||||||
def get_role(user_id):
|
class Users:
|
||||||
return load_users().get(str(user_id), {}).get("role", "guest")
|
def __init__(self, json_path: str = "./data/users.json"):
|
||||||
|
self._json_path: str = json_path
|
||||||
|
self._users = self._load_users()
|
||||||
|
|
||||||
def get_username(user_id):
|
def _load_users(self) -> dict[str, _User]:
|
||||||
return load_users().get(str(user_id), {}).get("username", None)
|
try:
|
||||||
|
with open(self._json_path, "r") as f:
|
||||||
|
return {uid: _User(**info) for uid, info in json.load(f)}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_fullname(user_id):
|
def _save_users(self) -> None:
|
||||||
return load_users().get(str(user_id), {}).get("fullname", None)
|
with open(self._json_path, "w") as f:
|
||||||
|
json.dump({uid: user.__dict__ for uid, user in self._users.items()}, f, default=str)
|
||||||
def set_credentials(user_id, username, password):
|
|
||||||
users = load_users()
|
def update_user(self, id: str, username: str, fullname: str) -> bool:
|
||||||
user = users.get(str(user_id), {})
|
if id in self._users.keys():
|
||||||
user["credentials"] = {"username": username, "password": password}
|
self._users[id].username = username
|
||||||
users[str(user_id)] = user
|
self._users[id].fullname = fullname
|
||||||
save_users(users)
|
else:
|
||||||
|
self._users[id] = _User(id, username, fullname)
|
||||||
def get_credentials(user_id):
|
self._save_users()
|
||||||
return load_users().get(str(user_id), {}).get("credentials")
|
return True
|
||||||
|
|
||||||
def get_grantor_credentials(user_id, gate):
|
def get_username(self, id: str) -> str:
|
||||||
from access_control import load_access
|
if id in self._users.keys():
|
||||||
access = load_access().get(str(user_id), {})
|
return self._users[id].username
|
||||||
entry = access.get(gate) or access.get("all")
|
|
||||||
if not entry:
|
|
||||||
return None
|
return None
|
||||||
grantor_id = entry.get("grantor")
|
|
||||||
return get_credentials(grantor_id)
|
def get_fullname(self, id: str) -> str:
|
||||||
|
if id in self._users.keys():
|
||||||
|
return self._users[id].fullname
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_status(self, id: str) -> Status:
|
||||||
|
if id in self._users.keys():
|
||||||
|
return self._users[id].status
|
||||||
|
return Status.DISABLED
|
||||||
|
|
||||||
|
def get_role(self, id: str) -> Role:
|
||||||
|
if id in self._users.keys():
|
||||||
|
return self._users[id].role
|
||||||
|
return Role.GUEST
|
||||||
|
|
||||||
def get_admins():
|
def get_credentials(self, id: str) -> Credential:
|
||||||
return [uid for uid, u in load_users().items() if u.get("role") == "admin"]
|
if id in self._users.keys():
|
||||||
|
return self._users[id].credentials
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_credentials(self, id: str, credentials: Credential) -> bool:
|
||||||
|
if id in self._users.keys():
|
||||||
|
self._users[id].credentials = credentials
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_open_gate(self, id: str, gate: str) -> bool:
|
||||||
|
if id in self._users.keys():
|
||||||
|
if gate in self._users[id].grants.keys():
|
||||||
|
if self._users[id].grants[gate].status == Status.ENABLED:
|
||||||
|
if self._users[id].grants[gate].expires_at > datetime.now(timezone.utc):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_grantor(self, id: str, gate: str) -> str:
|
||||||
|
if id in self._users.keys():
|
||||||
|
if gate in self._users[id].grants.keys():
|
||||||
|
return self._users[id].grants[gate].grantor
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_admins(self) -> list[str]:
|
||||||
|
return [uid for uid, user in self._users.items() if user.role == Role.ADMIN]
|
||||||
|
|
||||||
|
def grant_access(self, id: str, gate: str, expires_at: datetime, grantor_id: str) -> bool:
|
||||||
|
if id in self._users.keys():
|
||||||
|
self._users[id].grant(gate, _Grant(grantor_id, expires_at))
|
||||||
|
self._save_users()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def revoke_access(self, id: str, gate: str) -> bool:
|
||||||
|
if id in self._users.keys():
|
||||||
|
self._users[id].revoke(gate)
|
||||||
|
self._save_users()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
Reference in New Issue
Block a user