Updated API

This commit is contained in:
Corrado Mulas
2026-06-19 18:01:44 +02:00
parent c4a99d24e7
commit af2bafd645
5 changed files with 123 additions and 5 deletions

View File

@@ -69,6 +69,28 @@ docker build -t telegram-groupfactory .
docker run telegram-groupfactory docker run telegram-groupfactory
``` ```
## REST API
The userbot also exposes an internal FastAPI surface on `API_HOST:API_PORT`
for the separate conventional Telegram bot. Requests require `X-API-Key`.
Relevant bot-facing endpoints:
```text
POST /api/groups
GET /api/admin/default-users
PUT /api/admin/default-users
POST /api/admin/default-users
DELETE /api/admin/default-users
GET /api/admin/qr-backup?qr_group=default
PUT /api/admin/qr-backup
POST /api/admin/qr-backup/image
GET /api/admin/qr-groups
POST /api/admin/qr-groups/{qr_group}/assignments
DELETE /api/admin/qr-groups/assignments
POST /api/admin/qr-sync
```
## Documentation ## Documentation
- **[ADMIN_IMPLEMENTATION.md](ADMIN_IMPLEMENTATION.md)** - Admin-only configuration features - **[ADMIN_IMPLEMENTATION.md](ADMIN_IMPLEMENTATION.md)** - Admin-only configuration features

View File

@@ -4,6 +4,7 @@ python-dotenv>=0.19.0
fastapi>=0.95.0 fastapi>=0.95.0
uvicorn>=0.21.0 uvicorn>=0.21.0
pydantic>=1.10.0 pydantic>=1.10.0
python-multipart>=0.0.9
numpy>=1.26.0 numpy>=1.26.0
opencv-python-headless>=4.9.0.80 opencv-python-headless>=4.9.0.80
qrcode[pil]>=7.4.2 qrcode[pil]>=7.4.2

View File

@@ -1,10 +1,16 @@
import asyncio
import logging
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from fastapi import File, Form, Query, UploadFile
from src.api.auth import verify_api_key from src.api.auth import verify_api_key
from src.api.schemas import ( from src.api.schemas import (
AddUserRequest, AddUserRequest,
CommandResponse, CommandResponse,
QrBackupRequest, QrBackupRequest,
QrGroupAssignmentRequest,
QrSyncRequest,
UserIdsRequest, UserIdsRequest,
) )
@@ -14,6 +20,7 @@ def _envelope(message: str) -> CommandResponse:
router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(verify_api_key)]) router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(verify_api_key)])
logger = logging.getLogger(__name__)
def _admin_chat_id(request: Request) -> int: def _admin_chat_id(request: Request) -> int:
@@ -54,15 +61,93 @@ async def add_user_to_db(payload: AddUserRequest, request: Request):
@router.get("/qr-backup", response_model=CommandResponse) @router.get("/qr-backup", response_model=CommandResponse)
async def get_qr_backup(request: Request): async def get_qr_backup(request: Request, qr_group: str = Query("default")):
handler = request.app.state.admin_handler handler = request.app.state.admin_handler
return _envelope(await handler.handle_get_qr_backup(_admin_chat_id(request))) return _envelope(await handler.handle_get_qr_backup(_admin_chat_id(request), qr_group=qr_group))
@router.put("/qr-backup", response_model=CommandResponse) @router.put("/qr-backup", response_model=CommandResponse)
async def set_qr_backup(payload: QrBackupRequest, request: Request): async def set_qr_backup(payload: QrBackupRequest, request: Request):
handler = request.app.state.admin_handler handler = request.app.state.admin_handler
return _envelope(await handler.handle_set_qr_backup(_admin_chat_id(request), payload.qr_data)) return _envelope(await handler.handle_set_qr_backup(
_admin_chat_id(request),
payload.qr_data,
qr_group=payload.qr_group,
))
@router.post("/qr-backup/image", response_model=CommandResponse)
async def set_qr_backup_from_image(
request: Request,
qr_group: str = Form("default"),
file: UploadFile = File(...),
):
handler = request.app.state.admin_handler
image_bytes = await file.read()
return _envelope(await handler.handle_set_qr_backup_from_image(
_admin_chat_id(request),
image_bytes,
qr_group=qr_group,
))
@router.get("/qr-groups", response_model=CommandResponse)
async def list_qr_groups(request: Request, qr_group: str = Query(None)):
handler = request.app.state.admin_handler
return _envelope(await handler.handle_list_qr_groups(_admin_chat_id(request), qr_group=qr_group))
@router.post("/qr-groups/{qr_group}/assignments", response_model=CommandResponse)
async def assign_qr_group(qr_group: str, payload: QrGroupAssignmentRequest, request: Request):
handler = request.app.state.admin_handler
return _envelope(await handler.handle_assign_qr_group(
_admin_chat_id(request),
qr_group,
payload.group_ids,
))
@router.delete("/qr-groups/assignments", response_model=CommandResponse)
async def remove_qr_group_assignments(payload: QrGroupAssignmentRequest, request: Request):
handler = request.app.state.admin_handler
return _envelope(await handler.handle_remove_qr_group_assignment(
_admin_chat_id(request),
payload.group_ids,
))
@router.post("/qr-sync", response_model=CommandResponse)
async def sync_qr(payload: QrSyncRequest, request: Request):
sync_group = (payload.qr_group or "default").strip().lower()
current_task = request.app.state.api_background_tasks.get("grouphelp_qr_sync")
if current_task and not current_task.done():
return CommandResponse(ok=True, message=" GroupHelp QR sync is already running.")
group_service = request.app.state.group_handler.group_service
async def run_sync():
if sync_group == "all":
await group_service.sync_all_grouphelp_qr_groups(delay_seconds=30)
else:
await group_service.sync_grouphelp_qr_to_owned_groups(
delay_seconds=30,
qr_group=sync_group,
)
def sync_done(task):
try:
task.result()
except asyncio.CancelledError:
pass
except Exception as e:
logger.error("API GroupHelp QR sync failed: %s", e)
finally:
request.app.state.api_background_tasks["grouphelp_qr_sync"] = None
task = asyncio.create_task(run_sync(), name="api-grouphelp-qr-sync")
task.add_done_callback(sync_done)
request.app.state.api_background_tasks["grouphelp_qr_sync"] = task
return CommandResponse(ok=True, message=f"✅ GroupHelp QR sync `{sync_group}` started in background.")
@router.get("/help", response_model=CommandResponse) @router.get("/help", response_model=CommandResponse)

View File

@@ -1,4 +1,4 @@
from typing import List, Optional from typing import List, Optional, Union
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -25,8 +25,17 @@ class AddUsersToGroupRequest(BaseModel):
class UserIdsRequest(BaseModel): class UserIdsRequest(BaseModel):
user_ids: List[int] = Field(..., min_items=1) user_ids: List[Union[int, str]] = Field(..., min_items=1)
class QrBackupRequest(BaseModel): class QrBackupRequest(BaseModel):
qr_data: str = Field(..., min_length=1) qr_data: str = Field(..., min_length=1)
qr_group: Optional[str] = None
class QrGroupAssignmentRequest(BaseModel):
group_ids: List[Union[int, str]] = Field(..., min_items=1)
class QrSyncRequest(BaseModel):
qr_group: str = "default"

View File

@@ -28,6 +28,7 @@ def create_app(
app.state.user_handler = user_handler app.state.user_handler = user_handler
app.state.group_handler = group_handler app.state.group_handler = group_handler
app.state.admin_handler = admin_handler app.state.admin_handler = admin_handler
app.state.api_background_tasks = {"grouphelp_qr_sync": None}
@app.get("/health", tags=["meta"]) @app.get("/health", tags=["meta"])
async def health(): async def health():