Created modules and organized code

This commit is contained in:
2025-05-23 00:06:26 +02:00
parent 020b5e0193
commit 3088a75a7d
17 changed files with 340 additions and 248 deletions

View File

@@ -1,5 +1,5 @@
MIT License 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: 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:

View File

@@ -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)

View File

@@ -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
View File

@@ -1,200 +1,23 @@
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes, CallbackQueryHandler from functools import partial
from datetime import datetime
from config import BotConfig from config import BotConfig
from gates import Gates from models import Gates, Users
from users import Users, Credential, Role from handlers import *
BOT_USERNAME = "lagomareGateKeeperBot" bot_config = BotConfig("lagomareGateKeeperBot")
bot_config = BotConfig(BOT_USERNAME)
gates = Gates() gates = Gates()
users = Users() 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(): def main():
app = Application.builder().token(bot_config.token).post_init(post_init).build() app = Application.builder().token(bot_config.token).post_init(partial(post_init, bot_config=bot_config)).build()
app.add_handler(MessageHandler(None, updateuser), group=1) app.add_handler(MessageHandler(None, partial(updateuser, users=users)), group=1)
app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("start", partial(start, users=users)))
app.add_handler(CommandHandler("setcredentials", setcredentials)) app.add_handler(CommandHandler("setcredentials", partial(setcredentials, users=users)))
app.add_handler(CommandHandler("opengate", opengate)) app.add_handler(CommandHandler("opengate", partial(opengate, users=users, gates=gates)))
app.add_handler(CommandHandler("requestaccess", requestaccess)) app.add_handler(CommandHandler("requestaccess", partial(requestaccess, users=users)))
app.add_handler(CommandHandler("grantaccess", grantaccess)) app.add_handler(CommandHandler("grantaccess", partial(grantaccess, users=users)))
app.add_handler(CallbackQueryHandler(handle_main_menu_callback, pattern="^(open_gate_menu|request_access)$")) app.add_handler(CallbackQueryHandler(partial(handle_main_menu_callback, users=users, gates=gates), pattern="^(open_gate_menu|request_access)$"))
app.add_handler(CallbackQueryHandler(handle_gate_open_callback, pattern="^opengate_")) app.add_handler(CallbackQueryHandler(partial(handle_gate_open_callback, users=users, gates=gates), pattern="^opengate_"))
app.run_polling() app.run_polling()
if __name__ == "__main__": if __name__ == "__main__":

18
handlers/__init__.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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"
]

View File

@@ -1,9 +1,3 @@
from enum import Enum
class Status(Enum):
ENABLED = 1
DISABLED = 0
class Credential: class Credential:
def __init__(self, username: str, password: str): def __init__(self, username: str, password: str):
self.username = username self.username = username

View File

@@ -1,6 +1,7 @@
import json import json
from avconnect import AVConnectAPI from services import AVConnectAPI
from commons import Status, Credential from .status import Status
from .credential import Credential
class Gate: class Gate:
def __init__(self, id: str, name: str, status: Status = Status.ENABLED): def __init__(self, id: str, name: str, status: Status = Status.ENABLED):

5
models/status.py Normal file
View File

@@ -0,0 +1,5 @@
from enum import Enum
class Status(Enum):
ENABLED = 1
DISABLED = 0

View File

@@ -1,7 +1,8 @@
import json import json
from enum import Enum from enum import Enum
from datetime import datetime, timezone from datetime import datetime, timezone
from commons import Status, Credential from .status import Status
from .credential import Credential
class Role(Enum): class Role(Enum):
ADMIN = "admin" ADMIN = "admin"

5
services/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .avconnect import AVConnectAPI
__all__ = [
"AVConnectAPI"
]

View File

@@ -1,6 +1,6 @@
import requests import requests
from fake_useragent import UserAgent from fake_useragent import UserAgent
from commons import Credential from models import Credential
class AVConnectAPI: class AVConnectAPI:
_BASE_URL = "https://www.avconnect.it" _BASE_URL = "https://www.avconnect.it"